为什么列表初始化(使用花括号)比其他方法更好?


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

为什么?

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


12
为什么不使用auto
Mark Garcia

33
没错,这很方便,但是在我看来,这会降低可读性-我喜欢在读取代码时查看对象的类型。如果您100%确定对象是什么类型,为什么要使用auto?如果使用列表初始化(请阅读我的回答),则可以确保它始终正确。
Oleksiy

102
@Oleksiy:std::map<std::string, std::vector<std::string>>::const_iterator想和您谈谈。
Xeo

9
@Oleksiy我建议阅读此GotW
拉普兹

17
@doc我想说using MyContainer = std::map<std::string, std::vector<std::string>>;的更好(尤其是您可以使用它做模板!)
JAB

Answers:


356

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

列表初始化不允许缩小(第8.5.4节)。那是:

  • 一个整数不能转换为另一个不能保存其值的整数。例如,允许将char转换为int,但不允许将int转换为char。
  • 无法将浮点值转换为无法保存其值的另一种浮点类型。例如,允许将float翻倍,但不允许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 int
auto z2 = {99}; // z2 is std::initializer_list<int>
auto z3 = 99;   // z3 is an int

结论

除非您有充分的理由,否则将{}初始化优先于替代方案。


52
还有一个事实,()可以将using 解析为函数声明。您可以说T t(x,y,z);但不能混淆,令人困惑T t()。有时候,您可以确定x,甚至不能说T t(x);
juanchopanza

84
我强烈不同意这个答案。当您的类型带有ctor接受的大括号初始化变得完全混乱std::initializer_list。RedXIII提到了这个问题(只是将其清除),而您完全忽略了它。A(5,4)A{5,4}可以调用完全不同的函数,这是一件很重要的事情。它甚至可能导致调用看起来不直观。说{}默认情况下您应该首选,将导致人们误解发生了什么。不过,这不是你的错。我个人认为这是一个考虑周全的功能。
user1520427

13
@ user1520427这就是为什么会有“ 除非您有充分的理由不这样做 ”的原因。
Oleksiy 2015年

67
尽管这个问题很旧,但它受到了很大的冲击,因此,我在这里添加它只是为了参考(我在页面的其他地方都没有看到它)。从C ++ 14到新的从braced-init-list自动扣除的规则,现在可以编写,auto var{ 5 }并且最多可以推断intstd::initializer_list<int>
Edoardo Sparkon Dominici

12
哈哈,从所有评论来看,目前尚不清楚该怎么做。清楚的是,C ++规范是一团糟!
DrumM

113

关于使用列表初始化的好处已经有了很好的答案,但是我个人的经验法则是不要在可能的情况下使用花括号,而应使它依赖于概念上的含义:

  • 如果我要创建的对象从概念上讲包含我在构造函数中传递的值(例如,容器,POD结构,原子,智能指针等),那么我正在使用花括号。
  • 如果构造函数类似于常规函数调用(它执行由参数参数化的或多或少的复杂操作),那么我使用的是常规函数调用语法。
  • 对于默认初始化,我始终使用花括号。
    对于这种方式,我始终确保对象被初始化,而无论它是否是带有默认构造函数的“真实”类,无论如何它都会被调用或内置/ POD类型。第二,在大多数情况下,它与第一个规则一致,因为默认的初始化对象通常表示“空”对象。

以我的经验,与默认情况下使用花括号相比,可以更一致地应用此规则集,但是在无法使用它们或与带有括号的“常规”函数调用语法含义不同时,必须明确记住所有异常(调用其他重载)。

例如,它非常适合标准库类型,例如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

11
完全同意您的大部分回答。但是,您不认为将空括号放在vector上是多余的吗?我的意思是,当您需要对通用类型T的对象进行值初始化时,可以,但是对非通用代码执行此操作的目的是什么?
米哈伊尔

8
@Mikhail:这肯定是多余的,但是我的一个习惯是总是使局部变量初始化明确。正如我所写的那样,这主要是关于顺从性,因此在重要时我不会忘记它。我当然不会在代码审查中提到任何内容,也不会在样式指南中提到任何内容。
MikeMB

4
很干净的规则集。
laike9m

5
到目前为止,这是最好的答案。{}就像继承-容易滥用,导致难以理解的代码。
UKMonkey '18年

2
@MikeMB示例:const int &b{}<-不会尝试创建未初始化的引用,而是将其绑定到临时整数对象。第二个示例:struct A { const int &b; A():b{} {} };<-不会尝试创建未初始化的引用(而是()这样做),而是将其绑定到临时整数对象,然后使其悬空。即使有GCC,-Wall也不会针对第二个示例发出警告。
Johannes Schaub-litb

91

有很多原因可以使用大括号初始化,但是您应该知道,initializer_list<>构造函数比其他构造函数更可取,唯一的例外是default-constructor。这会导致构造函数和模板出现问题,其中类型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!!!
}

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


20
这是泛型编程中非常重要的一点。编写模板时,除非需要语义(好,并且可能用于默认构造对象),否则不要使用花括号初始列表(的标准名称)。{ ... }initializer_list
Xeo

82
老实说,我不明白为什么std::initializer_list规则甚至存在-它只会增加语言的混乱和混乱。有什么不好做Foo{{a}},如果你想在std::initializer_list构造函数?与std::initializer_list优先于所有其他重载相比,这似乎容易理解得多。
user1520427

5
上面的评论+1,因为我认为这真是一团糟!这不是逻辑;Foo{{a}}对于我来说,遵循一些逻辑远远不止于将Foo{a}其变成初始化器列表的优先级(用户可能会想到ups起来……)
Gabriel

32
基本上,C ++ 11将一个混乱替换为另一个混乱。哦,很抱歉,它不能代替它-它增加了它。您怎么知道您是否没有上过此类课程?如果开始时没有 std::initializer_list<Foo>构造函数,但将在某个时候将其添加Foo类中以扩展其接口怎么办?然后,Foo该类的用户被搞砸了。
doc

10
..“使用括号初始化的许多原因”是什么?这个答案指出一个原因(initializer_list<>),它并没有真正有资格说,它的首选,然后继续提在那里的一个很好的例子不是首选。我错过了〜30个其他人(截至2016-04-21)发现有帮助?
dwanderson '16

0

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

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

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


我尝试在goldbolt.org上四处闲逛,以使用提供的示例代码绕过“显式​​”或“私有”构造函数,并使一个或另一个私有或显式,并因适当的编译器错误而得到回报。愿意用一些示例代码来支持它吗?
Mark Storer

这是针对C ++ 20提出的问题的解决方法:open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1008r1.pdf
艾伦·詹森

1
如果您编辑答案以显示您正在谈论的C ++版本,那么我很乐意更改我的投票。
Mark Storer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.