为什么列表初始化(使用花括号)比替代品更好?

时间:2013-08-14 03:56:12

标签: c++ c++11 syntax initialization list-initialization

MyClass a1 {a};     // clearer and less error-prone than the other three
MyClass a2 = {a};
MyClass a3 = a;
MyClass a4(a);

为什么?

我在SO上找不到答案,所以让我回答一下我自己的问题。

4 个答案:

答案 0 :(得分:275)

基本上是从Bjarne Stroustrup的“C ++编程语言第4版”中复制和粘贴

列表初始化不允许缩小(§iso.8.5.4)。那就是:

  • 无法将整数转换为无法保存其值的另一个整数。例如,char 允许使用int,但不允许使用int。
  • 浮点值无法转换为无法保持其浮点类型的其他浮点类型 值。例如,允许浮动为double,但不允许浮动为double。
  • 无法将浮点值转换为整数类型。
  • 无法将整数值转换为浮点类型。

示例:

void fun(double val, int val2) {

    int x2 = val; // if val==7.9, x2 becomes 7 (bad)

    char c2 = val2; // if val2==1025, c2 becomes 1 (bad)

    int x3 {val}; // error: possible truncation (good)

    char c3 {val2}; // error: possible narrowing (good)

    char c4 {24}; // OK: 24 can be represented exactly as a char (good)

    char c5 {264}; // error (assuming 8-bit chars): 264 cannot be 
                   // represented as a char (good)

    int x4 {2.0}; // error: no double to int value conversion (good)

}

情况,其中=优先于{},当使用auto关键字来获取初始化程序确定的类型时。

示例:

auto z1 {99}; // z1 is an initializer_list<int>
auto z2 = 99; // z2 is an int

结论

除非您有充分理由不对其他选项进行初始化,否则优先选择{}

答案 1 :(得分:82)

使用大括号初始化有很多原因,但您应该知道 initializer_list<>构造函数优先于其他构造函数,异常是默认构造函数。这会导致构造函数和模板出现问题,其中类型T构造函数可以是初始化列表,也可以是普通的ctor。

struct Foo {
    Foo() {}

    Foo(std::initializer_list<Foo>) {
        std::cout << "initializer list" << std::endl;
    }

    Foo(const Foo&) {
        std::cout << "copy ctor" << std::endl;
    }
};

int main() {
    Foo a;
    Foo b(a); // copy ctor
    Foo c{a}; // copy ctor (init. list element) + initializer list!!!
}

假设您没有遇到此类,则没有理由不使用初始化列表。

答案 2 :(得分:73)

关于使用列表初始化的优点已经有了很好的答案,但是我个人的经验法则是尽可能不使用花括号,而是依赖于概念含义:

  • 如果我正在创建的对象在概念上保存了我在构造函数中传递的值(例如容器,POD结构,原子,智能指针等),那么我正在使用大括号。
  • 如果构造函数类似于普通函数调用(它执行一些由参数参数化的或多或少的复杂操作),那么我正在使用普通的函数调用语法。
  • 对于默认初始化,我总是使用花括号 首先,我总是确定该对象被初始化,而不管它是否例如是是一个“真正的”类,有一个默认的构造函数,无论如何都会被调用或者是内置/ POD类型。其次 - 在大多数情况下 - 与第一个规则一致,因为默认的初始化对象通常代表一个“空”对象。

根据我的经验,这个规则集可以比默认使用花括号更加一致地应用,但是当它们不能被使用或者具有与“普通”函数不同的含义时必须明确地记住所有异常。带括号的语法(调用不同的重载)。

例如非常适合标准库类型,如std::vector

vector<int> a{10,20};   //Curly braces -> fills the vector with the arguments

vector<int> b(10,20);   //Parenthesis -> uses arguments to parametrize some functionality,                          
vector<int> c(it1,it2); //like filling the vector with 10 integers or copying a range.

vector<int> d{};      //empty braces -> default constructs vector, which is equivalent
                      //to a vector that is filled with zero elements

答案 3 :(得分:-1)

只有您不使用-Wno-narrowing进行构建(如Google在Chromium中所做的那样),它才会更加安全。如果这样做,则安全性较低。如果没有该标志,只有不安全的情况将由C ++ 20修复。

注意: A)弯括号更安全,因为它们不允许变窄。 B)弯括号不安全,因为它们可以绕过私有或已删除的构造函数,并隐式调用显式标记的构造函数。

这两个组合意味着如果内部是基本常量,它们将更安全,但是如果它们是对象,则安全性将降低(尽管在C ++ 20中已固定)