何时使用括号括起来的初始化程序?


94

在C ++ 11中,我们具有用于初始化类的新语法,这为我们提供了许多初始化变量的可能性。

{ // Example 1
  int b(1);
  int a{1};
  int c = 1;
  int d = {1};
}
{ // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a{3,4};
  std::complex<double> c = {3,4};
  auto d = std::complex<double>(3,4);
  auto e = std::complex<double>{3,4};
}
{ // Example 3
  std::string a(3,'x');
  std::string b{3,'x'}; // oops
}
{ // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> b{std::plus<int>()};
}
{ // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> b{new int(5)};
}
{ // Example 6
  std::locale::global(std::locale("")); // copied from 22.4.8.3
  std::locale::global(std::locale{""});
}
{ // Example 7
  std::default_random_engine a {}; // Stroustrup's FAQ
  std::default_random_engine b;
}
{ // Example 8
  duration<long> a = 5; // Stroustrup's FAQ too
  duration<long> b(5);
  duration<long> c {5};
}

对于我声明的每个变量,我都必须考虑应该使用哪种初始化语法,这会降低我的编码速度。我确定这不是引入大括号的意图。

对于模板代码,更改语法可能导致不同的含义,因此正确的方法至关重要。

我想知道是否有一个通用的指南应该选择哪种语法。


1
来自{}初始化的意外行为示例:此处的
P i

Answers:


64

认为以下可能是一个很好的指南:

  • 如果您要初始化的(单个)值是对象的确切值,请使用copy(=)初始化(因为如果出现错误,您将永远不会意外地调用显式构造函数,该构造函数通常会解释提供的值不同地)。在复制初始化不可用的地方,请检查花括号初始化是否具有正确的语义,如果可以,请使用正确的语义。否则,请使用括号初始化(如果该方法也不可用,则无论如何您还是不走运)。

  • 如果您要初始化的值是要存储在对象中的值的列表(如向量/数组的元素或复数的实/虚部),请使用花括号初始化(如果有)。

  • 如果要初始化的值不是要存储的值,而是描述对象的预期值/状态,请使用括号。示例是的大小参数vector或的文件名参数fstream


4
@ user1304032:语言环境不是字符串,因此您不会使用副本初始化。语言环境也不包含字符串(它可能将该字符串存储为实现细节,但这不是其目的),因此您不会使用括号初始化。因此,该指南说使用括号初始化。
celtschk 2012年

2
我个人最喜欢该准则,并且在通用代码上也很好用。有一些例外(T {}或语法原因,例如最令人讨厌的parse),但总的来说,我认为这是一个很好的建议。请注意,这是我的主观意见,因此也应该看看其他答案。
helami 2012年

2
@celtschk:不适用于不可复制,不可移动的类型;type var{};做。
ildjarn

2
@celtschk:我并不是说它会经常发生,但是它的键入较少,并且可以在更多上下文中使用,那么不利之处是什么?
ildjarn

2
我的指南当然绝不要求复制初始化。;-]
ildjarn 2012年

26

我敢肯定,永远不会有通用的准则。我的方法是使用大括号记住

  1. 初始化程序列表构造函数优先于其他构造函数
  2. 所有标准库容器和std :: basic_string具有初始化程序列表构造函数。
  3. 大括号初始化不允许缩小转换范围。

因此,圆括号和花括号不可互换。但是,知道它们的不同之处,使我可以在大多数情况下使用圆括号大括号初始化(在某些情况下,我目前不是编译器错误)。


6
花括号的缺点是我可以错误地调用列表构造函数。圆括号没有。这不是默认情况下使用圆括号的原因吗?
helami 2012年

4
@user:关于,int i = 0;我认为没人会int i{0}在那里使用它,这可能会造成混淆(同样,0如果是type int,那么就不会缩小)。对于其他所有内容,我都会遵循Juancho的建议:更喜欢{},请注意一些您不应该使用的情况。请注意,将初始化程序列表用作构造函数参数的类型并不多,可以期望容器和类似容器的类型(元组...)具有它们,但是大多数代码会调用适当的构造函数。
大卫·罗德里格斯(DavidRodríguez)-dribeas 2012年

3
@ user1304032这取决于您是否关心缩小。我愿意,所以我更喜欢编译器告诉我这int i{some floating point}是一个错误,而不是默默地截断。
juanchopanza'4

3
关于“首选{},请注意少数情况下不应该使用的情况”:假设两个类在语义上等效,但一个类也具有初始化列表。是否应该以不同的方式调用两个等效的构造函数?
helami 2012年

3
@helami:“假设两个类具有语义上等效的构造函数,但一个类也具有初始化列表。两个等效的构造函数是否应以不同的方式调用?” 假设我遇到了最烦人的解析;任何实例的任何构造函数都可能发生这种情况。如果只用{}“初始化”来表示,除非绝对不能,否则避免这种情况会容易得多。
Nicol Bolas 2012年

16

在通用代码(即模板)之外,您可以(而且我也可以)在任何地方使用花括号。优点之一是它可以在任何地方使用,例如即使是在类内初始化:

struct foo {
    // Ok
    std::string a = { "foo" };

    // Also ok
    std::string b { "bar" };

    // Not possible
    std::string c("qux");

    // For completeness this is possible
    std::string d = "baz";
};

或对于函数参数:

void foo(std::pair<int, double*>);
foo({ 42, nullptr });
// Not possible with parentheses without spelling out the type:
foo(std::pair<int, double*>(42, nullptr));

对于变量,我在T t = { init };T t { init };样式之间不太关注,我发现两者之间的差别很小,最坏的情况是只会产生有用的有关滥用explicit构造函数的编译器消息。

对于std::initializer_list显然可以接受的类型,有时std::initializer_list需要非构造函数(经典示例为std::vector<int> twenty_answers(20, 42);)。最好不要使用括号。


对于通用代码(即在模板中),最后一段应该提出一些警告。考虑以下:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T { std::forward<Args>(args)... } }; }

然后auto p = make_unique<std::vector<T>>(20, T {});,如果产生大小为2的向量T是例如int,或大小20的矢量,如果Tstd::string。一个非常有说服力的迹象表明,这里发生了非常错误的事情,就是没有任何特征可以在这里拯救您(例如,使用SFINAE):std::is_constructible就直接初始化而言,而我们正在使用括号初始化,它可以直接仅当没有构造函数进行std::initializer_list干扰时才进行初始化。同样std::is_convertible没有帮助。

我研究了是否可以手动滚动一个可以解决该问题的特征,但我对此并不感到过分乐观。无论如何,我认为我们不会错过太多东西,我认为make_unique<T>(foo, bar)构成等效的事实T(foo, bar)非常直观。特别是考虑到这make_unique<T>({ foo, bar })是完全不同的,并且仅当foobar具有相同的类型时才有意义。

因此,对于通用代码,我仅使用大括号进行值初始化(例如T t {};T t = {};),这非常方便,并且我认为优于C ++ 03方式T t = T();否则,它要么是直接初始化语法(即T t(a0, a1, a2);),要么是默认构造(T t; stream >> t;这是我认为的唯一使用情况)。

但是,这并不意味着所有括号都不好,请考虑带有修复程序的先前示例:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{ return std::unique_ptr<T> { new T(std::forward<Args>(args)...) }; }

std::unique_ptr<T>即使实际类型取决于模板参数,它仍然使用花括号来构造T


@interjay我的一些例子可能确实需要使用无符号的类型,而不是,例如make_unique<T>(20u, T {})用于T为任何unsignedstd::string。不太确定细节。(请注意,我还评论了有关直接初始化与括号初始化有关例如完善转发功能的期望。)std::string c("qux");尚未指定为类内初始化以避免语法中成员函数声明的歧义。
Luc Danton '04

@interjay我不同意第一点,请随时检查8.5.4列表初始化和13.3.1.7通过列表初始化进行初始化。至于第二个,您需要仔细研究我写的内容(与类内初始化有关)和/或C ++语法(例如member-declarator,它引用brace-or-equal-initializer)。
Luc Danton '04

嗯,您是对的-我之前在GCC 4.5上进行了测试,这似乎可以证实我的意思,但是GCC 4.6确实同意您的观点。我确实错过了您在谈论类初始化的事实。我很抱歉。
interjay 2012年
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.