章节3:拥抱现代C++

章节3:拥抱现代C++

item 7:区别()与{ }两种对象创建方法

在C++中,对象的创建方法可以有以下几种:

int x(0);		//① 使用括号初始化
int y = 0;		//② 使用等号初始化
int z { 0 };	/③ 用中括号初始化
int z =  {0};	//本质还是中括号初始化;本文不予考虑;

常见误解之一:②处发生了复赋值。
解释:对于内置类型而言(比如int),二者的差距是学术上的,可以忽略。对于自定用户自定义类型而言,初始化与赋值不可同日而语,因为二者调用不同的函数:

widget w1;			//调用默认构造函数;
widget w2 = w1;		//不是赋值;调用拷贝构造函数;
w1 = w2;			//是赋值;调用拷贝运算符 = 

尽管有许多的初始化语法,C++98在某些时候还是不能产生我们想要的初始化。比如:直接创造(create)一个拥有特定值的STL容器是不可能的。(含糊)

为了解决这种多种初始化语法的含糊场景、创造一个可以覆盖所有初始化场景的语法,C++11引入了“通用初始化”:中括号初始化(braced initialization)。从概念上来说,“中括号初始化”这一语法结构可以用在任何地方,表达任何东西。使用这一语法,容器的初始化可以这么写:

std::vector<int> v{1,2,3};	//v的初始化内容为:1,2,3

class {
    ……
private:
   int x{0};		//x的初始化值为0;
    int y = 0;		//作用同上;
    int z(0);		//error!
}

C++中还引入了这样的新特性:对于非静态成员而言,{ }可以指定默认初始值
代码如上 。即使是不可拷贝的对象,如 std::atomic<int> ai1 {0}; 也可以用中括号初始化,代码如上。需要尤为注意的是:中括号禁止隐式缩小转换implicit narrowing conversions 。代码如下:

double x,y,z;
……
int sum1{x+y+z};	//error! sun of doubles may not be 		    				 //expressible as int 

int sum1(x+y+z);//ok,value of expression truncated to in int
int sum3 = x+y+z;//ditto

与此同时,(),= 确允许这样做。否则原有的合法代码就会出问题。代码如上。

其次,{ } 的优势还体现在它对C++谜之解析的免疫 原文:most vexing parse 。C++有这样的一条规则:anything that can be parsed as a declaration must be interpreted as one 这一规则带来的直接副作用就是“谜之解析”,具体表现如下:

widget w1(10);		//①以10为参数调用构造函数;

widget w2();		//②调用默认构造函数;

widget w3{};		//③调用默认构造函数;

②出代码的目的是比较清晰的,但是由于C++的规则,实际上发生的事是:声明了一个名为w2的函数,函数签名为:widget()[^1] 。但是书写成③的形式则可以完成默认构造。

总结上文,不难得出结论:除了需要narrowing conversion的场合,其他时候应当尽可能的使用{ } 进行初始化 。那么,标题应该改为“优先选择 { }初始化”?

中括号初始化当然是有缺点的:initializer_lists和构造器重载之间的苟且关系
孩子没娘,说来话长。且听Meyers徐徐道来。

重温item2[^2] ,当使用auto推到{ }构造时auto x = {1,2,3}; ,x所得并非基础类型int,而是模板 std::initializer_lists。这只是引子,由这一现象继续深挖下去。
当构造函数不存在std::initializer_lists时,(),{ }表现是一致的。如下:

class widget{
public:
	widget(int i, bool b);
    widget(int i, double d);
    ……
}
widget w1(10,true);	//一号构造;
widget w2{10, true}; //ditto
widget w3{10,5.0};	//二号构造

上面这些东西并不难理解,单纯的构造函数重载而已。但是当std::initializer_lists横插一脚时,奸情发生了。如下:

class widget{
public:
	widget(int i, bool b);
    widget(int i, double d);
    widget(std::initializer_list<long double> ld);
    
    operator float() const;
    ……
}
widget w1(10,true);	//as before;
widget w2{10, true}; //三号构造
widget w3{10,5.0};	//三号构造
widget w4{w4};		//三号构造
widget w5{std::move(w4)};//三号构造

我的Meyer![^3]不管是类型不符合,或是由类型转换强行分离调用者与构造函数之间的关系,只要构造函数中有参数std::initializer_lists,{ }初始化必然是优先调用它,哪怕是经过大量的类型转换。除非,构造函数是这样:
widget (std::initializer_list<std::string> str)
这样以来,std::string 与build_in type 明显不能再相互转换,此时{ }才恢复正常的构造函数调用——按照参数的匹配情况。
这时又存在一个额外情况:

class widget{
   widget();
    widget(std::initializer_list<int> il);
    ……
}
widget w1;		//默认构造
widget w2{};	//默认构造!!
widget w3();	//most vexing parse

widget w4({});	//重载构造;
widget w5{{}};	//ditto

调用构造函数且无参数时,统一调用默认构造函数 ,此时再想调用std::initializer_lists 需要采用w4,w5的形式。

应用。说了这么多语法的情况,无非是要用。可以用在那里嘞?看代码:

std::vector<int> v1(10,20);
//vector中有10个元素,每个元素都是20;
std::vector<int> v2{10,20};
//vector中有2个元素,分别时10,20;

收获。

一:大多数情况下,std::initializer_lists<int>不是compete other overloads, 而是overshadow others;
二:
():避免了auto出std::initializer_lists<int>;
    分离了std::initializer_lists<int>构造和普通构造。
{}:immunity to c++ most vexing parse;
    prohibition of narrowing conversion;
选择一个做日常使用,另一个则正在have to的时候使用;

————————————————————————————————
感觉比较长,翻译着玩。


***

[^1]: 函数签名(function signature),是指函数的返回值,形参。这是作者meyers 在书中的规定。
[^2]: 指书籍 effective modern C++
[^3]: 谐音梗,妈耶 和 Meyers的部分发音类似 :laughing:
***
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值