对于一个类而言,只要其中包含有初始化列表的构造函数,编译器在编译使用{}语法的构造时会最倾向于调用初始化列表构造函数,哪怕做类型转换也在所不惜,哪怕有类型最佳匹配的普通构造函数或移动构造函数也会被劫持
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{{}}; // 同上
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