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

Why is list initialization (using curly braces) better than the alternatives?

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

为什么?

我找不到答案,所以让我自己回答我的问题。


基本上从Bjarne Stroustrup的《C++程序设计语言第四版》中复制和粘贴:

列表初始化不允许缩小范围(§ISO.8.5.4)。即:

  • 一个整数不能转换为另一个不能保存其值的整数。例如,char允许to int,但不允许int to char。
  • 不能将浮点值转换为另一个不能保存其值的浮点类型价值。例如,允许float to double,但不允许double to float。
  • 浮点值无法转换为整数类型。
  • 整数值无法转换为浮点类型。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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关键字获取初始值设定项确定的类型时,才会优先使用=而不是。

例子:

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

结论

首选初始化而不是替代方案,除非您有充分的理由不这样做。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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!!!
}

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


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

  • 如果我在概念上创建的对象持有我在构造函数中传递的值(例如容器、pod结构、原子、智能指针等),那么我将使用大括号。
  • 如果构造函数类似于普通函数调用(它执行一些或多或少复杂的操作,这些操作由参数参数参数化),那么我使用的是普通函数调用语法。
  • 对于默认初始化,我总是使用大括号。首先,通过这种方式,不管对象是一个"真实"类,它都会被初始化,其中包含一个默认的构造函数,可以无论如何调用它,或者是一个内置的/pod类型。其次,在大多数情况下,它与第一个规则一致,因为默认的初始化对象通常表示一个"空"对象。

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

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

1
2
3
4
5
6
7
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