慎用容器的 List Initializing

C++11 中引进了一种叫 List Initializing 的技术,C++ Primer 5th3.3.1. Defining and Initializing vectors 中很是肤浅的介绍了一下它的形式:ios

cppvector<string> articles = {"a", "an", "the"}; // or
vector<string> articles{"a", "an", "the"};

书中颇为推崇这样的初始化方式,且形式上更加贴近 C 语言中 array 的初始化过程,非常亲民。c++


但咱们简单作个实验,来讲明这种形式在效率上可能存在的问题:函数

cpp#include <iostream>
#include <vector>

struct X {
    X() { std::cout << "X()" << std::endl; }
    X(const X&) { std::cout << "X(const X&)" << std::endl; } // copy constructor
    ~X() { std::cout << "~X()" << std::endl; }
}

int main()
{
    X x;
    std::vector<X> vec{x, x};
}

猜猜看,这段程序的输出是什么?ui

X()
X(const X&)
X(const X&)
X(const X&)
X(const X&)
~X()
~X()
~X()
~X()
~X()

看看有没有猜对呢?c++11

有没有人好奇,为何 copy constructor 被调用了四次? 咱们但愿它被调用多少次呢?很显然,是两次。code

咱们对 main 函数稍做修改:对象

cppint main()
{
    X x;
    std::vector<X> vec;
    vec.reserve(2);
    vec.push_back(x);
    vec.push_back(x);
}

而后查看本次输出:get

X()
X(const X&)
X(const X&)
~X()
~X()
~X()

的确变成了咱们但愿的两次。若是有兴趣的话,能够将 reserve 那句话给注释掉试试,会发现调用次数变成了3次,但那不属于我们本次探讨的范畴,若是有疑问能够留言讨论。string

第二次虽然多写了几行,但貌似极大程度上的避免了 copy constructor 的频繁调用,想象一下若初始化列表里躺着一大堆 x 会有什么下场。it


揭秘

copy constructor 的调用次数为什么会翻倍?

由于 List Initializing 本质上是先基于列表中的元素,构造出一个 initializer_list, 这个类型也是 c++11 引入的,能够看看详细定义

而后,再将构造出来的 initializer_list 中的元素逐一 copy 至容器中。

故:

cppstd::vector<X> vec{x, x};

至关于:

cppstd::initializer_list<X> list = {x, x};  // copy 2 times
std::vector<X> vec(list); // copy 2 times

真相大白。


结论

对于非 built-in 对象来讲, 使用 List Initializing 付出的代价,在某些状况下不容忽视。 过去陈旧的写法,反而让人更加踏实。

相关文章
相关标签/搜索