普通构造函数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中的情况。应该尽可能做到用户无论用小括号还是花括号进行初始化都不会产生区别。一定要慎重考虑新出现的初始化列表构造函数对其他构造函数的影响!!!
  • 作为类库使用者,必须认真的考虑小括号和花括号之间选择创建对象的方式,最好选择其中一个从一而终
05-16 12:08