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

Why?


当前回答

使用大括号初始化的原因有很多,但您应该意识到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!!!
}

假设您没有遇到这样的类,那么几乎没有理由不使用初始化器列表。

其他回答

只要你不像铬中的谷歌那样使用- wno -narrow构建,它就更安全。如果你这样做了,那就不太安全了。如果没有这个标志,c++ 20将会解决唯一不安全的情况。

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

这两者结合起来意味着,如果里面是基本常量,它们会更安全,但如果是对象,它们就不那么安全了(尽管在c++ 20中进行了修正)。

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

If the object I'm creating conceptually holds the values I'm passing in the constructor (e.g. containers, POD structs, atomics, smart pointers etc.), then I'm using the braces. If the constructor resembles a normal function call (it performs some more or less complex operations that are parametrized by the arguments) then I'm using the normal function call syntax. For default initialization I always use curly braces. For one, that way I'm always sure that the object gets initialized irrespective of whether it e.g. is a "real" class with a default constructor that would get called anyway or a builtin / POD type. Second it is - in most cases - consistent with the first rule, as a default initialized object often represents an "empty" object.

根据我的经验,在默认情况下,这个规则集的应用比使用花括号更一致,但是当它们不能使用或具有与“正常”函数调用语法不同的含义时(调用不同的重载),必须显式地记住所有异常。

例如,它很适合标准库类型,如std::vector:

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

vector<int> b(10, 20);   //Parentheses -> 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

使用大括号初始化的原因有很多,但您应该意识到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!!!
}

假设您没有遇到这样的类,那么几乎没有理由不使用初始化器列表。

更新(2022-02-11): 请注意,在最初发布的(下面)的主题上,最近有更多的意见,反对{}初始化式的偏好,例如Arthur Dwyer在他的博客文章《c++中初始化的噩梦》中。

最初的回答:

阅读Herb Sutter(更新)的GotW #1。 本文详细解释了这些选项和其他几个选项之间的区别,以及与区分不同选项的行为相关的几个陷阱。

摘自第四节的要点:

When should you use ( ) vs. { } syntax to initialize objects? Why? Here’s the simple guideline: Guideline: Prefer to use initialization with { }, such as vector v = { 1, 2, 3, 4 }; or auto v = vector{ 1, 2, 3, 4 };, because it’s more consistent, more correct, and avoids having to know about old-style pitfalls at all. In single-argument cases where you prefer to see only the = sign, such as int i = 42; and auto x = anything; omitting the braces is fine. … That covers the vast majority of cases. There is only one main exception: … In rare cases, such as vector v(10,20); or auto v = vector(10,20);, use initialization with ( ) to explicitly call a constructor that is otherwise hidden by an initializer_list constructor. However, the reason this should be generally “rare” is because default and copy construction are already special and work fine with { }, and good class design now mostly avoids the resort-to-( ) case for user-defined constructors because of this final design guideline: Guideline: When you design a class, avoid providing a constructor that ambiguously overloads with an initializer_list constructor, so that users won’t need to use ( ) to reach such a hidden constructor.

请参阅关于该主题的核心指南:ES.23:首选{}-初始化式语法。

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

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

整数不能转换为不能保存其值的另一个整数。例如,char To int是允许的,但int To char不允许。 浮点值不能转换为不能保存其值的其他浮点类型 价值。例如,允许float到double,但不允许double到float。 浮点值不能转换为整型。 整数值不能转换为浮点类型。

例子:

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 int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

结论

优先选择{}初始化而不是其他选项,除非有充分的理由不这样做。