當需要可變參數,如果使用vector的話,可能會遇到下面這個問題。函數f有兩個重載的版本,編譯器無法選擇具體調用vector還是list的版本。
而使用initializer_list的話,就不會出現錯誤了。編譯器優先匹配了initializer_list的版本。
vector有push_back函數,也就是說vector可以在函數里面修改,所以必然vector必須在heap上分配空間來存儲數據。而initializer_list只有begin和end函數,函數內并不能修改它,所以編譯器有機會在stack上存儲initializer_list的數據來提高性能。
vector是值語義,也就是說拷貝一個vector,那里面的元素也會被拷貝一次。而initializer_list是指針語義,里面的元素并不會被拷貝。比如說下面這段代碼list和list2的begin其實指向了同一個空間。這樣的設計是合理的,因為initializer_list是不可修改的,沒有理由再拷貝一次。
指針語義的好處是,下面這段遞歸函數不會對里面的元素產生很多次復制。雖然每次都構造了一個新的initializer_list,但是里面的數值{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }并沒有經過復制。
在C++11的時候,大家都想加上一個值列表的東西,就像{ value1, value2, value2... valueN }一樣。一種想法是搞出一個新的關鍵字,給C++增加一個新的build-in類型。但是新的關鍵字很有可能會導致老的程序無法被編譯,如果湊巧老的程序使用了那個關鍵字做名字。于是C++11的做法是,只是在標準模板庫里面增加一個新的模板initializer_list,然后讓編譯器遇到{1,2,3,4}這種東西的時候,隱式的轉換成一個initializer_list的對象。下面這段代碼輸出的是class std::initializer_list<int>
有了這個基本的東西以后,剩下的問題就可以在已有的框架里面解決了。比如說實現std::vector<int> v = { 1, 2, 3 };這個功能。其實就是編譯器遇到{ 1, 2, 3 }就生成了一個initializer_list,然后調用了vector對應的一個構造函數.
vector( std::initializer_list<T> init, const Allocator& alloc = Allocator() );
回到最初的
f和g的例子,g({ 1, 2, 3, 4 });沒有編譯錯誤,因為有一個最佳的匹配。f({ 1, 2, 3, 4 });出現了編譯錯誤,因為沒有最佳的一個匹配,編譯器面臨著隱式類型轉換,但是有兩個選擇,vector和list,所以就有編譯錯誤了。
新聞熱點
疑難解答