普通構造函數VS初始化列表構造函數 初始化列表構造函數最優先匹配問題 對於一個類而言,只要其中包含有初始化列表的構造函數,編譯器在編譯使用{}語法的構造時會最傾向於調用初始化列表構造函數,哪怕做類型轉換也在所不惜,哪怕有類型最佳匹配的普通構造函數或移動構造函數也會被劫持 class Widget { ...
普通構造函數VS初始化列表構造函數
初始化列表構造函數最優先匹配問題
對於一個類而言,只要其中包含有初始化列表的構造函數,編譯器在編譯使用{}語法的構造時會最傾向於調用初始化列表構造函數,哪怕做類型轉換也在所不惜,哪怕有類型最佳匹配的普通構造函數或移動構造函數也會被劫持
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il);
operator float() const;
};
Widget w1(10, true); // 使⽤小括弧初始化
//調⽤第⼀個構造函數
Widget w2{10, true}; // 使⽤花括弧初始化
// 調⽤第三個構造函數
// (10 和 true 轉化為long double)
Widget w3(10, 5.0); // 使⽤小括弧初始化
// 調⽤第二個構造函數
Widget w4{10, 5.0}; // 使⽤花括弧初始化
// 調⽤第三個構造函數
// (10 和 5.0 轉化為long double)
Widget w5(w4); // 使⽤小括弧,調⽤拷⻉構造函數
Widget w6{w4}; // 使⽤花括弧,調⽤std::initializer_list構造函數
Widget w7(std::move(w4)); // 使⽤小括弧,調⽤移動構造函數
Widget w8{std::move(w4)}; // 使⽤花括弧,調⽤std::initializer_list構造函數
編譯器這種熱衷於把括弧初始化與初始化列表構造函數匹配的行為,會導致一些莫名其妙的錯誤
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<bool> il); // element type is now bool
… // no implicit conversion funcs
};
Widget w{10, 5.0}; //錯誤!要求變窄轉換,int(10)double(5.0)無法轉換為bool類型
只有在沒有辦法把{}中實參的類型轉化為初始化列表時,編譯器才會回到正常的函數決議流程中
⽐如我們在構造函數中⽤std::initializer_list<std::string>
代替std::initializer_list<bool>
,這時⾮std::initializer_list構造函數將再次成為函數決議的候選者,因為沒有辦法把int和bool轉換為std::string:
class Widget {
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<std::string> il);
…
};
Widget w1(10, true); // 使⽤小括弧初始化,調⽤第⼀個構造函數
Widget w2{10, true}; // 使⽤花括弧初始化,調⽤第⼀個構造函數
Widget w3(10, 5.0); // 使⽤小括弧初始化,調⽤第⼆個構造函數
Widget w4{10, 5.0}; // 使⽤花括弧初始化,調⽤第⼆個構造函數
{}空初始化列表會發生什麼
假如{}內是空的,類中既有預設構造函數,也有初始化列表構造函數,此時{}會被視為沒有實參,而不是一個空的初始化列表,因此會調用預設構造函數。如果就是想調用初始化列表構造函數,這應該使用{{}}的方式
class Widget {
public:
Widget();
Widget(std::initializer_list<int> il);
...
};
Widget w1; // 調⽤預設構造函數
Widget w2{}; // 同上
Widget w3(); // 最令⼈頭疼的解析!聲明⼀個函數
Widget w4({}); // 調⽤std::initializer_list
Widget w5{{}}; // 同上
初始化列錶帶來的vector的坑
std::vector<int> v1(10, 20); //使⽤⾮std::initializer_list
//構造函數創建⼀個包含10個元素的std::vector
//所有的元素的值都是20
std::vector<int> v2{10, 20}; //使⽤std::initializer_list
//構造函數創建包含兩個元素的std::vector
//元素的值為10和20
初始化列表構造函數問題帶來的兩點啟示
- 作為類庫作者,如果在構造函數中重載了一個或多個初始化列表構造函數,要考慮用戶使用{}初始化的情況,最好避免類似std::vector中的情況。應該儘可能做到用戶無論用小括弧還是花括弧進行初始化都不會產生區別。一定要慎重考慮新出現的初始化列表構造函數對其他構造函數的影響!!!
- 作為類庫使用者,必須認真的考慮小括弧和花括弧之間選擇創建對象的方式,最好選擇其中一個從一而終