在创建对象时注意区分()和{}
指定初始化的方式包括使用小括号,使用等号,或者使用大括号。
int x(0); //初始化物在小括号内
int y = 0; //初始化物在等号之后
int z{ 0 }; //初始化物在大括号内
//在很多情况下,使用一个等号和一对大括号也是可以的
int z = { 0 };
使用一个等号来书写初始化语句往往会让人意味这里面会发生一次赋值,但其实并没有。
Widget w1; //调用默认构造函数
Widget w2 = w1; //并非赋值,调用的是拷贝构造函数
w1 = w2; //并非赋值,调用的是拷贝赋值运算符
c++11引入了统一初始化:单一的,至少从概念上可以用于一切场合,表达一切意思的初始化,它的基础是大括号形式。
std::vector<int> v{1, 3, 5}; //v的初始内容为1,3,5
大括号同样可以为非静态成员指定默认初始化值,这项能力也可以使用“=”的初始化语法,却不能使用小括号。
class Widget{
...
private:
int x{0};
int y = 0;
int z(0); //不可行
};
不可复制对象(如std::atomic
类型的对象),可以采用大括号和小括号来进行初始化,却不能使用“=”。
std::atomic<int> ai1{ 0 }; //可行
std::atomic<int> ai2(0); //可行
std::atomic<int> ai3 = 0; //不可行
大括号初始化有一项新特性,就是它禁止内建类型之间进行隐式窄化类型转换。如果大括号内的表达式无法保证能够采用进行初始化的对象来表达,则代码不能通过编译。
double x, y, z;
int sum1{ x+y+z; }; //错误,double类型之和无法用Int表达
采用小括号和“=”的初始化则不会进行窄化类型转换检查。
C++规定:任何能够解析为声明的都要解析为声明,这会带来副作用。
最令人苦恼的解析语法就是,程序员本来想要以默认方式构造一个对象,结果却不小心声明了一个函数。这个错误的根本原因在于构造函数调用语法。
当你想要以传参数的方式调用构造函数时,可以这样写
Widget w1(10);
但如果你试图用对等语法来调用一个没有形参的Widget构造函数的话,那结果却变成声明了一个函数而非对象;
Widget w2(); //最令人苦恼的解析语法
//这个语句声明了一个名为w2,返回一个Widget类型的函数
由于函数声明不能使用大括号来指定形参列表,所以使用大括号来完成对象的默认构造没有上面这个问题。
Widget w3{}; //调用没有形参的Widget构造函数
在构造函数被调用时,只要形参中没有任何一个具备std::initializer_list
类型,那么大括号和小括号的意义就没有区别。
class Widget{
public:
Widget(int i,bool b); //构造函数的形参中没有任何一个具备
Widget(int i, double b); //std::initializer_list类型
...
};
Widget w1(10,true); //调用第一个构造函数
Widget w2{10, true}; //调用第一个构造函数
Widget w3(10, 5.0); //调用第二个构造函数
Widget w4{10, 5.0}; //调用的第二个构造函数
如果,有一个或多个构造函数声明了任何一个具备std::initializer_list
类型的形参,那么采用了大括号初始化语法的调用语句会强烈地优先选用带有std::initializer_list
类型形参的重载版本。
class Widget{
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il);
...
};
Widget w1(10,true); //使用小括号,调用的是第一个构造函数
Widget w2{10,true}; //使用大括号,调用的是带有
//std::initializer_list类型形参的构造函数
//10和true被强制转换为long double
Widget w3(10,5.0); //使用小括号,调用的是第二个构造函数
Widget w4{10, 5.0}; //使用大括号,调用的是带有
//std::initializer_list类型形参的构造函数
//10和5.0被转换为long double
复制或移动的构造函数也可能被带有std::initializer_list
类型形参的构造函数劫持。
class Widget{
public:
Widget(int i, bool b);
Widget(int i, double d);
Widget(std::initializer_list<long double> il);
operator float() const; //强制转换成float类型
...
};
Widget w5(w4); //调用拷贝构造函数
Widget w6{w4}; //使用大括号,调用的是带有
//std::initializer_list类型形参的构造函数
//w4的返回值被强制转换成float,随后float又被
//强制转换成long double
Widget w7(std::move(w4)); //使用小括号,调用移动构造函数
Widget w8{ std::move(w4); }; //使用大括号,调用的是带有
//std::initializer_list类型形参的构造函数
编译器想要把大括号初始化物匹配带有std::initializer_list
类型形参的构造函数的决心是如此强烈,以至于最优选的带有std::initializer_list
类型形参的构造函数无法被调用时,这种决心还是会占上风。
class Widget{
public:
Widget(int i,bool b);
Widget(int i,double d);
Widget(std::initializer_list<bool> il);
};
Widget w(10,5.0); //错误,要求窄化类型转换。
这里,编译器会忽略前两个构造函数(第二个构造函数的形参表和实参表的类型是精确匹配的),转而尝试带有一个std::initializer_list<bool>
类型形参的构造函数。而要调用该函数就要求把一个int(10)
和一个double(5.0)
强制转换成bool
类型,而这两个强制类型转换都是窄化的。并且窄化类型转换在大括号初始化内部是禁止的,所以这个调用不合法。
只有在找不到任何办法把大括号初始化物中的实参转换为std::initializer_list
模板中的类型时,编译器才会其检查普通的重载函数。
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}; //使用大括号,调用的是第二个构造函数
假如你使用了一对空大括号来构造一个对象,而该对象既支持默认构造函数,又支持带有std::initializer_list
类型形参的构造函数。那么,这对空大括号的意义是什么呢?如果意义是“没有实参”,那就应该执行默认构造;但如果意义是“空的std::initializer_list
”,那就应该以一个不含任何元素的std::initializer_list
为基础执行构造。
语言规定,在这种情况下应该执行默认构造函数。空大括号对表示的是“没有实参”,而不是“空的std::initializer_list
”。
class Widget{
public:
Widget(); //默认构造函数
Widget(std::initializer_list<int> il); //带有std::initializer_list类型形参的构造函数
};
Widget w1; //调用的是默认构造函数
Widget w2{}; //调用的是默认构造函数
Widget w3(); //变成函数声明语句
如果你的确想要调用一个带有std::initializer_list
类型形参的构造函数,并传入一个空的std::initializer_list
。可以通过把空大括号作为构造函数实参的方式实现这个目的,即把一对空大括号放入一对小括号或大括号的方式来清楚地表明你传递的是什么;
Widget w4({}); //带有std::initializer_list类型形参的构造函数
//传入一个空的std::initializer_list
Widget w5{{}}; //同上
要点速记
- 大括号初始化可以应用的语境最为宽泛,可以阻止隐式窄化类型转换,还可以对最令人苦难的解析语法免疫
- 在构造函数重载决议期间,只要有任何可能,大括号初始化物就会与带有
std::initializer_list
类型的形参相匹配,即使其他重载版本有着貌似更加匹配的形参表 - 使用小括号还是大括号,会造成结果大相径庭的一个例子是:使用两个实参来创建一个
std::vector<数值类型>
对象