c++ initializer_list踩坑


c++11后引入了uniform initialization的概念,按照它說的,任何的初始化操作都可以借由大括號{}搞定。
比如在c++98時代,我們會用:

int i[] = {1,3,4,5,6};  //初始化數組

class Cat
{
public:
  Cat(int old, int weight):old(old),weight(weight){};
  int old, weight;
};
Cat c1(12,32); //初始化類實例

Dog d1= 13; // 參數化式地初始化類實例(單參數)
Dog d2 = d1; // 拷貝構造函數初始化類示例

總之,初始化不同的東西,有各種各樣的語法,c++11干脆把所有的初始化都用{}實現吧。

int i[]{1,2,3,4};


Cat c1{12,32};

// Dog類似
Dog d1{d3};
Dog d2{d1};

坑,大坑

似乎一切歲月靜好,無腦用大括號就完事了。但考慮下面這樣的情況:

vector<int> vi{2,3}; //執行完這句,vi里是2,3還是3,3呢?

c++98的程序員知道vector有個構造函數是這樣的:

vector( size_type count, const T& value, const Allocator& alloc = Allocator());

就是說初始化時填入count個value。
那么按照大括號里的東西就是構造函數的參數的邏輯,vi構造完的結果是3,3。
但是實際上是2,3。太坑了,太坑了。

如果我們想弄出3,3。得寫成

vector<int> vi(2,3);

怎么一會兒大括號里是構造函數的參數,一會兒又不是,煩死了。

解釋

具體原因就是編譯器看到被大括號包起來的東西,會將其轉化成initializer_list 類型,initializer_list也就是相當於個容器。例如:

{1,2,3}  --------> initializer_list<int>

即編譯器見到前者,就會把其轉換成后者。

如果類沒有以initializer_list 為參數的構造函數時,那編譯器會把initializer_list 拆包,這個時候 {}()沒有任何區別,該調用哪個調用哪個,直接將 {}看成 ()就完事。
但是,如果類有initializer_list 為參數的構造函數,根據編譯器的機制,編譯器就會優先調用該構造函數,把一整包initializer_list直接丟進該構造函數去,讓該函數處理。

重點來了,為什么vector vi{2,3} 不等同於 vector vi(2,3)呢? ,就是因為c++11后的stl容器類,都實現了以initailizer_list為參數的構造函數。以gcc5.4.0的stl實現為例:

 373       vector(initializer_list<value_type> __l,
 374          const allocator_type& __a = allocator_type())
 375       : _Base(__a)
 376       {
 377     _M_range_initialize(__l.begin(), __l.end(),
 378                 random_access_iterator_tag());
 379       }

既然有這個構造函數,遇見{}時就該調用它,把initializer_list一整包丟進去。而非調用像上面的

vector( size_type count, const T& value, const Allocator& alloc = Allocator());

相關知識

c++11中{}語法的出現,也導致了多實參非explicit ctor構造函數可能會在隱式轉換時調用。在以前,只有one argument non-explicit ctor才有可能會在隱式轉換時調用,因此我們加個explicit就阻止了這種轉換。但是c++11后編譯器會將{}自動解包(如果沒有initializer_list為參的構造函數時),可能解包出來多個東西。因此將multi-argument ctor前面加上explicit就變得有必要了。

一些bb

我覺得這個設計有些問題,當用第三方類庫的時候,鬼知道它有沒有寫initializer_list為參的構造函數。本來我只是想開開心心用{}的語法調用普通構造函數,結果不小心調用了initializer_list的版本,佛了。感情我寫{}之前還得看看類庫的源代碼是吧。
最后我想出了一個解決辦法,估計也是設計者想讓我們用的吧,就是正常創建類對象時,用()就完事。只有真正當我知道該類(例如stl容器)提供了初始化列表的版本,並且我知道其行為,並且我真的需要它的時候,我才用{}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM