何时使用支架封闭的初始化器?

hel*_*ami 91 c++ initializer-list c++11

在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};
}
Run Code Online (Sandbox Code Playgroud)

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

在模板代码方面,更改语法可能会产生不同的含义,因此以正确的方式进行是至关重要的.

我想知道是否存在一个应该选择语法的通用指南.

cel*_*chk 63

认为以下内容可能是一个很好的指导方针:

  • 如果要初始化的(单个)值是对象的确切值,请使用copy(=)初始化(因为如果出现错误,您将永远不会意外地调用显式构造函数,这通常会解释提供的值不同).在没有复制初始化的地方,请查看大括号初始化是否具有正确的语义,如果是,请使用; 否则使用括号初始化(如果这也不可用,那么你运气不好).

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

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

  • @ user1304032:语言环境不是字符串,因此您不会使用复制初始化.语言环境也不包含字符串(它可能将该字符串存储为实现细节,但这不是它的用途),因此您不会使用大括号初始化.因此,指南说要使用括号初始化. (4认同)
  • 我个人最喜欢这个指南,它也适用于通用代码.有一些例外("T {}"或句法原因,如[最令人烦恼的解析](http://en.wikipedia.org/wiki/Most_vexing_parse)),但总的来说,我认为这是一个很好的建议.请注意,这是我的主观意见,所以我们也应该看看其他答案. (2认同)
  • @celtschk:这对不可复制的不可移动类型不起作用; `type var {};`的确如此. (2认同)
  • @celtschk:我不是说这是经常会发生的事情,但它更少打字并且在更多情境中工作,那么它的缺点是什么呢? (2认同)
  • _My_准则肯定不会要求复制初始化.; - ] (2认同)
  • 我只是指出了`auto var = type(args);`中的缺陷,并显示了可行的替代语法.即使有一个容易修复的缺陷,仍然遵守指导方针被称为[货物崇拜编程](http://en.wikipedia.org/wiki/Cargo_cult_programming),当然不值得赞同.我没有投票,也没有打算争论它; 你应该很高兴有人愿意对你的答案留下建设性的意见,因为它只会提高答案的质量. (2认同)

jua*_*nza 26

我很确定永远不会有通用指南.我的方法是始终使用花括号记住它

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

所以圆形和花括号不可互换.但是知道它们的不同之处允许我在大多数情况下使用大括号初始化(在某些情况下我当前不能编译器错误).

  • 卷曲大括号的缺点是我可以错误地调用列表构造函数.圆括号没有.这不是默认使用圆括号的理由吗? (6认同)
  • @user:在`int i = 0;`我认为没有人会在那里使用`int i {0}`,这可能会令人困惑(同样,`0`如果是'int`类型,那么就会不要*缩小*).对于其他一切,我会遵循Juancho的建议:更喜欢{},要注意你不应该做的少数情况.请注意,没有那么多类型将初始化列表作为构造函数参数,您可以期望容器和类似容器的类型(元组...)拥有它们,但大多数代码将调用适当的构造函数. (4认同)
  • @ user1304032这取决于你是否关心缩小范围.我这样做,所以我更喜欢编译器告诉我`int i {some floating point}`是一个错误,而不是默默地截断. (3认同)
  • 关于"prefer {},请注意少数情况下你不应该":假设两个类具有语义上等效的构造函数,但是一个类也有一个初始化列表.这两个等价的构造函数应该被不同地调用吗? (3认同)
  • @helami:"假设两个类有一个语义上等效的构造函数,但是一个类也有一个初始化列表.两个等价的构造函数是否应该以不同的方式调用?" 说我遇到了最令人烦恼的解析; 任何实例的*any*构造函数都可能发生这种情况.如果你只是使用`{}`来表示"初始化",除非你绝对*不能*,否则更容易避免这种情况. (3认同)

Luc*_*ton 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";
};
Run Code Online (Sandbox Code Playgroud)

或者用于函数参数:

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));
Run Code Online (Sandbox Code Playgroud)

对于变量,我在这些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)... } }; }
Run Code Online (Sandbox Code Playgroud)

然后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)...) }; }
Run Code Online (Sandbox Code Playgroud)

这仍然使用大括号来构造std::unique_ptr<T>,即使实际类型依赖于模板参数T.