【C++】初始化列表建構函式VS普通建構函式

縉雲燒餅發表於2023-05-16

普通建構函式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中的情況。應該儘可能做到使用者無論用小括號還是花括號進行初始化都不會產生區別。一定要慎重考慮新出現的初始化列表建構函式對其他建構函式的影響!!!
  • 作為類庫使用者,必須認真的考慮小括號和花括號之間選擇建立物件的方式,最好選擇其中一個從一而終

相關文章