慎用容器的 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 付出的代價,在某些狀況下不容忽視。 過去陳舊的寫法,反而讓人更加踏實。

相關文章
相關標籤/搜索