条款7:在创建对象时注意区分`()`与`{}`

指定初始化值的方式包括使用小括号、使用等号、或使用大括号:

int x(0);  	//初始化物在小括号内
int y = 0; 	//初始化物在等号之后
int x{0};	//初始化物在大括号之内

对于int这样的内置类型来说,初始化和赋值无区别,但是对于用户自定义的类型,初始化和赋值就有区别了:

Widget w1;		// 调用的是默认构造函数
Widget w2 = w1;	// 并非赋值,调用的是复制构造函数
w1 = w2;		// 并非赋值,调用的是赋值运算符

大括号同样可以用来为非静态成员制定默认初始化值,也可以使用“=,却不能使用小括号

class Widget {
private:
	int x{0};
	int y = 0;
	int x(0);  // 无法通过编译
};

不可以复制的对象(如std::atomic类型的对象)可以使用大括号和小括号进行初始化,却不能使用“=”:

std::atomic<int> ai1{0};	// 正确
std::atomic<int> ai2(0);	// 正确
std::atmoic<int> ai3 = 0;	// 错误

大括号初始化有一项新特性,就是它禁止内建类型之间进行隐式窄化类型转化。如果大括号内的表达式无法保证能够采用进行初始化的对象来表达,则代码不能通过编译;

double x,y,z;
int sum1{x + y + z}; // 错误!double类型之和可能无法用int表达

而采用“小括号”和“=”的初始化则不会进行窄化类型转换检查,因为如果那样的话就会破坏太多遗留代码;

int sum2(x + y + z);	// 没问题(表达式被截断为int)
int sum3 = x + y + z;	// 同上

C++规定:任何能够解析为声明的到要解析为声明,而这会带来副作用。比如程序员本来想要以默认方式构造一个对象,结果却不小心声明了一个函数:
当你想以传参方式调用构造函数时,可以这样写:

Widget w1(10);		// 调用Widget的构造函数,传入形参10

但如果你试图用对等语法来调用一个没有形参的Widget构造函数的话,那结果却变成声明了一个函数而非对象:

Widget w2(); 		// 这个语句声明了一个名为w2,返回一个Widget类型对象的函数

由于函数声明不能使用大括号来制定形参列表,所以用大括号来完成对象的默认构造没有上述问题;

Widget w3{};		// 调用没有形参的Widget构造函数

在构造函数被调用时,只要形参没有任何一个具备std::initializer_list类型,那么小括号和大括号的意义就没有区别;

class Widget {
publicWidget(int i, bool b);
	Widget(int i, double d);
};

Widget w1(10, true);	// 调用的是第一个构造函数
Widget w2{10, true};	// 调用的是第一个构造函数
Widget w3(10, 5.0);		// 调用的是第二个构造函数
Widget w4{10, 5.0};		// 调用的是第二个构造函数

如果,有一个或多个构造函数声明了任何一个具备std::initializer_list类型的形参,那么采用了大括号初始化语法的调用语句会强烈地优先选用带有std::inittializer_list类型形参的重载版本。

class Widget {
publicWidget(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 {
publicWidget(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类型参数的构造函数(w4的返回值被强制转换为float,随后float又被强制转换

编译器想要把大括号初始化物匹配带有std::initializer_list类型形参的构造函数的决心是如此强烈;举个例子

class Widget {
publicWidget(int i, bool b);
	Widget(int i, double d);
	Widget(std::initializer_list<bool> il);	
};

Widget w{10, 5.0};	// 错误!要求窄化类型转换

编译器会忽略前两个构造函数, 转而尝试第三个构造函数,然而要调用改构造函数就要求把int(10)double(5.0)强制转化为bool类型。而这两个强制转化类型都是窄化的(bool无法精确表示这两个值中的任何一个),并且窄化类型转化在大括号初始化物内部都是禁止的
只有在找不到任何办法把大括号初始化物中实参转化为std::initializer_list模板中的类型时,编译器才会退而去检查普通的重载决议;如比:

class Widget {
publicWidget(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};		// 调用的是第二个构造函数b

有个边界用例需要提及,假定你用一对空括号来构造一个对象,而该对象既支持默认构造函数,又支持带有std::intializer_list类型参数的构造函数,这时候该调用哪个呢?语言规定,在这种情形下应该执行默认构造函数。空大括号对表示的是“没有实参”,而非空的std::initializer_list:

class Widget {
publicWidget(i);
	Widget(std::initializer_list<int> il);	
};

Widget w1;		// 调用默认构造函数
Widget w2{}; 	// 调用默认构造函数
Widget w2();	// 令人苦恼,变成了函数声明语句

如果你的确想要调用一个带有std::initialzier_list类型参数的构造函数,并传入一个空的std::initizlizer_list的话,你可以通过把空大括号对作为构造实参的方式实现这个目的;

Widget w4({});
Widget w5{{}};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值