创建对象时,()和{}有何区别?
摘要:本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢! 博客已经迁移到 "这里啦" 从不同的角度
本文翻译自modern effective C++,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
博客已经迁移到这里啦
从不同的角度来看,在C++11中,对象初始化拥有多种语法选择,这体现了语法丰富造成的尴尬或者烂摊子。一般情况下,初始化的值可以用圆括号,等号,花括号来确定:
int x(0); //用圆括号初始化
int y = 0; //用"="初始化
int z{ 0 }; //用花括号初始化
在很多情况下,也可以使用等号加花括号的形式:
int z = { 0 }; //用"="和花括号初始化
在这个Item中,对于剩下的这种情况我通常忽略“等号加花括号”的语法,因为C++通常把它和“只使用花括号”的情况同样对待。
”烂摊子“指的是使用等号来初始化常常误导C++初学者,这里发生了赋值操作(尽管不是这样的)。对于built-in类型比如int,只是学术上的不同,但是对于user-defined类型,把初始化和赋值区分开来很重要,因为它们涉及不同的函数:
Widget w1; //调用默认构造函数
Widget w2 = w1; //不是赋值,调用拷贝构造函数
w1 = w2; //是赋值,调用operator=
尽管这里有好几个初始化的语法了,C++98仍然没有办法去做到一些想得到的初始化。举个例子,我们没有办法直接指示一个STL容器使用特定的集合来创建(比如1,3,和5)。
为了解决多种初始化语法之间的混乱,也为了解决他们没有覆盖所有的初始化情况,C++介绍了一种标准初始化:至少在概念上,它是一个单一的初始化语法,可以用在任何地方,做到任何事情。它基于花括号,所以因为这个原因,我更喜欢用术语花括号初始化来形容它。”标准初始化“是一个想法,”花括号初始化“是一个语法概念。
花括号初始化让你做到之前你做不到的事,使用花括号,明确容器的初始内容是很简单的:
std::vector<int> v{ 1, 3, 5}; //v的初始内容是1,3,5
花括号同样可以用来明确non-static成员变量的初始值。这是C++11的新能力,也能用”=“初始化语法做到,但是不能用圆括号做到:
class Widget{
...
private:
int x{ 0 }; //对的,x的默认值为0
int y = 0; //同样是对的
int z(0); //错误!
另外,不能拷贝的对象(比如,std::atomics---看 Item 40)能用花括号和圆括号初始化,但是不能用”=“初始化:
std::atomic<int> ai1{ 0 }; //对的
std::atomic<int> ai2(0); //对的
std::atomic<int> ai3 = 0; //错误
因此这很容易理解为什么花括号初始化被称为”标准“。因为,C++中指定初始化值的三种方式中,只有花括号能用在每个地方。
花括号初始化有一个新奇的特性,它阻止在built-in类型中的隐式收缩转换(narrowing conversation)。如果表达式的值不能保证被初始化对象表现出来,代码将无法通过编译:
double x, y, z;
...
int sum1{ x + y + z }; //错误!doubles的和不能表现为int
用圆括号和”=“初始化不会检查收缩转换(narrowing conversation),因为这么做的话,会让历史遗留的代码无法使用:
int sum2(x + y + z); //可以(表达式的值被截断为int)
int sum3 = x + y + z; //同上
花括号初始化的另外一个值得一谈的特性是它能避免C++最令人恼火的解析。一方面,C++的规则中,所有能被解释为声明的东西肯定会被解释为声明,最令人恼火的解析常常折磨开发者,当开发者想用默认构造函数构造一个对象时,他常常会不小心声明了一个函数。问题的根本就是你想使用一个参数调用一个构造函数,你能这么做:
Widget w1(10); //使用参数10调用Widget的构造函数
但是如果你使用类似的语法,尝试使用0个参数调用Widget的构造函数,你会声明一个函数,而不是一个对象:
Widget w2(); //最令人恼火的解析!声明一个
//名字是w2返回Widget的函数
函数不能使用花括号作为参数列表来声明,所以使用花括号调用默认构造函数构造一个对象不会有这样的问题:
Widget w3{}; //不带参数调用Widget的默认构造函数
因此这里对于花括号初始化有很多可以说的。
