条款7.在创建对象时注意区分()和{}

本文详细介绍了C++中大括号初始化的广泛适用性,如何防止隐式窄化类型转换,以及在构造函数重载选择中的优先级。重点讨论了大括号如何决定初始化行为,尤其是与std::initializer_list形参的匹配和避免误解的解析问题。
摘要由CSDN通过智能技术生成

在创建对象时注意区分()和{}

指定初始化的方式包括使用小括号,使用等号,或者使用大括号

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<数值类型>对象
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值