Saturday, 02. September 2017 06:46PM
# Item 7: Distinguish between () and {} when creating objects.
# 条目7:创建对象时,仔细区分 () 与 {} 的不同 。

术语&概念

  • ()

    parentheses, 圆括号。

  • {}

    braces, 花括号

==Example==
~~~c++
int x(0);

int z{ 0 }; //Braced initialization
~~~

快速笔记

  1. 取决于个人观点不同,C++11中体现出的初始化对象的语法选择,可谓丰富到令人尴尬或者说是一团令人困惑之物。
    • 圆括号,等号,花括号,等号+花括号
    • 有些情况实际上是copy constructor被调用,而C++新手(newbies)会错认为是copy assignment
    • 虽然C++98也有好几种初始化语法,但是还是有些情况使其语法不能胜任。比如,直接将一组特定的值指定给STL容器中的元素作为初值。
  2. 三连胜般的好处(A trifecta of of goodness)列举!Brace initialization的好处有:
    • 最广泛多样的使用上下文
    • 禁止内建类型(built-in type)的隐式窄向转型(narrowing conversion)。
    • 免疫C++最恼人的解析(C++’s most vexing parse)问题
  3. 既然这么好,本条目为何不改名叫“宁愿使用花括号初始化语法”(Prefer braced initialization syntax)?
    因为这个语法的缺点就是:时有“惊喜”相伴!当braced initializer, std::initializer_lists和构造函数重载决议(constructor overload resolution)三者互相作用时,会导致代码看上去应该做的事情与实际做的事情南辕北辙。比如Item 2中auto的例子,事实上你越喜欢auto,就越少对使用花括号初始化上心。
    • 当没有涉及std::initializer_list形参时,圆括号和花括号含义相同
    • 一旦存在以std::initializer_list为形参的构造函数时,只要使用花括号那么编译器就会强烈的倾向使用该重载(override)。只要有任何可能的手段,编译器就会采用那样的解释。
    • 有时即使存在最佳匹配,编译器也会为了能匹配上std::initializer_list而试图去进行一次窄向转型最终失败。即使编译失败也不去匹配正确答案。
    • 只有完全没可能转型时,编译器才会放弃初始列(std::initializer_list)而转向其他重载。
  4. 注意一个罕见情况:当使用一组空的花括号构造对象时,指的是
  • 空的参数列表?
  • 一个空的初值列(std::initializer_list)?
    答案是空的参数列表。记住这里空花括号表示没有参数,而不是指有一个空的初值列作为参数。
  • 表示空的初值列需要额外一个花括号。
  1. 关于std::initializer_list这些信息对日常编程影响能有多大呢?很大。std::vector中就有一个这样的构造函数。尤其是vector接受两个参数时。作为告诫,
    • 作为类(class)的作者要避免设计出仅仅因选用括号的不同就会造成重载决议不同的类。并且在给原有的类添加以初值列为形参的重载构造函数时要审慎地决定。问题不仅在于添加初值列重载会改变既存代码的行为(任何重载都会),而在于初值列参数版本的函数不是简单地参与竞争,而是给其他重载蒙上了巨大阴影。
    • 作为类的使用者,要小心选择这两种语法。大多数开发者选择主要使用其中之一,只有必要时才用另一个。
    • 对于模板(template)作者来说,尤其沮丧。通常无法知道应该使用哪种。

例子 (未完成)

  1. 为了处理多种初始化语法带来的混乱已及支持一些特殊使用场景。C++11引入uniform initialization语法,一种能(至少在概念上能)用于任何地方表达任何东西的初始化语法。
    这是基于花括号(braces)的语法,因此我们偏好选择术语braced initialization。uniform initialization是个概念。Braced initialization是一个句法结构。
    举例三种使用场景,现有的语法中只有Braced initialization可以适用于所有情况。也就不难理解为何叫uniform initialization了。
    • 指定一组值来初始化容器元素
    • 为非静态数据成员指定默认初值
    • 初始化uncopyable的对象

谨记要点

  • 花括号初始化(Braced initialization)语法,适用范围最广,预防窄向转型,免疫C++之最恼人解析(most vexing parse)。
  • 碰到构造函数重载决议时, 花括号语法会使编译器极尽所有可能去匹配std::initializer_list, 即使有其他更好的匹配也无济于事。
  • 一个例子:当用两个参数去创建一个std::vector<numeric type>时,选择圆括号还是花括号会使语义有重大不同。
  • 为模板内部的对象创建行为选择其中一种语法很有挑战性。