何时使用大括号初始化程序?

在C ++ 11中,我们有了初始化类的新语法,这给我们提供了大量的如何初始化variables的可能性。

{ // 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}; } 

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

当涉及到模板代码时,更改语法可能会导致不同的含义,所以正确的方法至关重要。

我想知道是否有一个通用的指导方针,应该select哪种语法。

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

  • 如果您正在初始化的(单个)值旨在成为对象的确切值 ,请使用copy( = )初始化(因为在发生错误的情况下,您将永远不会意外地调用显式构造函数,这通常会解释提供的价值不同)。 在复制初始化不可用的地方,看看括号初始化是否有正确的语义,如果是的话,使用它; 否则使用圆括号初始化(如果这还不可用,无论如何,你是运气不好)。

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

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

我很确定,永远不会有一个通用的指导方针。 我的方法是总是使用花括号来记住这一点

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

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

通用代码(即模板)之外,你可以(和我)在任何地方使用大括号 。 其中一个优点是它可以在任何地方工作,例如即使是在课堂上的初始化:

 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)); 

对于variables我不太在意T t = { init }; 或者T t { init }; 样式,我发现区别是轻微的,最坏的情况下,只会导致有用的编译器消息滥用explicit构造函数。

对于接受std::initializer_listtypes,显然有时需要使用非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 {}); 如果T是例如int ,则创build大小为2的向量;如果Tstd::string ,则创build大小为20的向量。 一个非常明显的迹象表明,在这里发生了一些非常错误的事情,那就是没有什么特性可以在这里保存(比如用SFINAE): std::is_constructible是直接初始化,而我们使用的是大括号初始化当且仅当没有构造函数采用std::initializer_list干涉时才推迟直接std::initializer_list 。 同样, std::is_convertible没有任何帮助。

我已经调查过,如果事实上是可能的,可以修复这个问题,但我并不太乐观。 无论如何,我认为我们不会错过太多的东西,我认为make_unique<T>(foo, bar)结果相当于T(foo, bar)结构非常直观。 特别是考虑到make_unique<T>({ foo, bar })是非常不相似的,只有当foobar具有相同的types时才有意义。

因此, 对于generics代码,我只使用花括号来进行值初始化 (例如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)...) }; } 

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