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

Posted

技术标签:

【中文标题】何时使用大括号封闭的初始化程序?【英文标题】:When to use the brace-enclosed initializer? 【发布时间】:2012-04-16 03:25:36 【问题描述】:

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

 // Example 1
  int b(1);
  int a1;
  int c = 1;
  int d = 1;

 // Example 2
  std::complex<double> b(3,4);
  std::complex<double> a3,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 b3,'x'; // oops

 // Example 4
  std::function<int(int,int)> a(std::plus<int>());
  std::function<int(int,int)> bstd::plus<int>();

 // Example 5
  std::unique_ptr<int> a(new int(5));
  std::unique_ptr<int> bnew 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;

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

对于模板代码,改变语法会导致不同的含义,所以走对路很重要。

我想知道是否存在一个应该选择哪种语法的通用准则。

【问题讨论】:

初始化的意外行为示例:string(50, 'x') vs string50, 'x' here 【参考方案1】:

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

如果您正在初始化的(单个)值是对象的精确值,请使用复制 (=) 初始化(因为如果出现错误,您永远不会意外调用显式构造函数,它通常以不同方式解释提供的值)。在复制初始化不可用的地方,看看大括号初始化是否有正确的语义,如果是,就使用它;否则使用括号初始化(如果这也不可用,那么无论如何你都不走运)。

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

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

【讨论】:

@user1304032:语言环境不是字符串,因此您不会使用复制初始化。语言环境也不包含字符串(它可能将该字符串存储为实现细节,但这不是它的目的),因此您不会使用大括号初始化。因此指南说要使用括号初始化。 我个人最喜欢这个指南,它也适用于通用代码。有一些例外(T 或像most vexing parse 这样的语法原因),但总的来说,我认为这是一个很好的建议。请注意,这是我的主观意见,所以大家也应该看看其他答案。 @celtschk :这不适用于不可复制、不可移动的类型; type var; 会。 @celtschk :我并不是说它会经常发生,但它的输入更少并且适用于更多的上下文,那么缺点是什么? 我的指南当然从不要求复制初始化。 ;-]【参考方案2】:

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

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

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

【讨论】:

花括号的缺点是我会错误地调用列表构造函数。圆括号没有。这不是默认使用圆括号的理由吗? @user:在int i = 0; 上,我认为没有人会在那里使用int i0,这可能会令人困惑(另外,0 是如果类型为int,那么会有不要缩小)。对于其他一切,我会遵循 Juancho 的建议:更喜欢 ,注意少数不应该这样做的情况。请注意,将初始化列表作为构造函数参数的类型并不多,您可以期望容器和类似容器的类型(元组...)拥有它们,但大多数代码将调用适当的构造函数。 @user1304032 这取决于您是否关心缩小范围。我愿意,所以我更喜欢编译器告诉我int isome floating point 是一个错误,而不是默默地截断。 关于“更喜欢 ,注意少数不应该使用的情况”:假设两个类有一个语义等价的构造函数,但一个类也有一个初始化列表。两个等价的构造函数是否应该以不同的方式调用? @helami:“假设两个类有一个语义等价的构造函数,但一个类也有一个初始化列表。这两个等价的构造函数是否应该以不同的方式调用?”假设我遇到了最令人烦恼的解析;这可能发生在任何实例的 any 构造函数上。如果你只使用 来表示“初始化”,那么避免这种情况会容易得多,除非你绝对不能【参考方案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));

对于变量我不太注意T t = init ;T t init ; 样式之间的差异,我发现差异很小,最坏的情况下只会导致关于滥用explicit 构造函数的有用编译器消息。

对于接受std::initializer_list 的类型,尽管显然有时需要非std::initializer_list 构造函数(经典示例是std::vector&lt;int&gt; 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)...  ; 

如果T 是例如,auto p = make_unique&lt;std::vector&lt;T&gt;&gt;(20, T ); 创建一个大小为 2 的向量。 int,如果 Tstd::string,则为大小为 20 的向量。一个非常明显的迹象表明这里发生了一些非常错误的事情,那就是没有 no 特征可以在这里拯救你(例如使用 SFINAE):std::is_constructible 是直接初始化,而我们'重新使用大括号初始化,它推迟到直接初始化当且仅当没有构造函数采用std::initializer_list 干扰。同样std::is_convertible 也无济于事。

我已经调查过是否真的可以手动滚动一个可以解决该问题的特性,但我对此并不太乐观。无论如何,我认为我们不会遗漏太多东西,我认为make_unique&lt;T&gt;(foo, bar) 导致构造等同于T(foo, bar) 的事实非常直观;特别是考虑到 make_unique&lt;T&gt;( foo, bar ) 非常不同,并且只有在 foobar 具有相同类型时才有意义。

因此对于通用代码我只使用大括号进行值初始化(例如T t ;T t = ;),这非常方便并且我认为优于C++03 方式T t = T(); . 否则它要么是直接初始化语法(即T t(a0, a1, a2);),要么有时是默认构造(T t; stream &gt;&gt; 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&lt;T&gt;,即使实际类型取决于模板参数T

【讨论】:

@interjay 我的一些示例可能确实需要使用无符号类型,例如make_unique&lt;T&gt;(20u, T ) 代表 Tunsignedstd::string。不太清楚细节。 (请注意,我还评论了关于直接初始化与大括号初始化的期望,例如完美转发函数。)std::string c("qux"); 没有被指定为类内初始化,以避免语法中成员函数声明的歧义。 @interjay 我不同意你的第一点,请随意检查 8.5.4 列表初始化和 13.3.1.7 列表初始化初始化。至于第二个,你需要仔细看看我写的(关于 in-class 初始化)和/或 C++ 语法(例如 member-declarator , 它引用 brace-or-equal-initializer)。 嗯,你是对的 - 我之前用 GCC 4.5 进行测试,这似乎证实了我所说的,但 GCC 4.6 确实同意你的观点。我确实错过了你在谈论课堂初始化的事实。我很抱歉。

以上是关于何时使用大括号封闭的初始化程序?的主要内容,如果未能解决你的问题,请参考以下文章

大括号封闭的初始化列表构造函数

phpStorm中如何不让其自动添加封闭大括号?

如何修复警告:初始化程序周围缺少大括号?

试编写一个算法从检查一个Java语言中的大括号方括号小括号是不是配对,若能够全?

使用功能组件时使用大括号与圆括号制作组件

数组必须用大括号括起来的初始化程序 c++ 初始化