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

时间:2012-04-02 12:58:35

标签: c++ c++11 initializer-list

在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};
}

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

说到模板代码,更改语法会导致不同的含义,因此以正确的方式进行是至关重要的。

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

3 个答案:

答案 0 :(得分:59)

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

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

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

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

答案 1 :(得分:25)

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

  1. 初始化列表构造函数优先于其他构造函数
  2. 所有标准库容器和std :: basic_string都有初始化列表构造函数。
  3. 大括号初始化不允许缩小转换次数。
  4. 所以圆形和花括号不可互换。但是知道它们的不同之处允许我在大多数情况下使用大括号初始化(有些情况下我当前不能编译错误)。

答案 2 :(得分: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 };样式之间没有太多关注,我发现差别很小,最坏的情况只会导致有关滥用{{{}的有用编译器消息1}}构造函数。

对于接受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的向量。如果Tint,则T或大小为20的向量。一个非常明显的迹象表明这里存在一些非常错误的信息是 no 特性可以在这里保存(例如使用SFINAE):std::string是直接初始化的,而我们正在使用大括号初始化,它遵循直接初始化当且仅当没有构造函数采用std::is_constructible干扰时。同样地,std::initializer_list没有任何帮助。

我已经调查过,实际上是否有可能手动推出可以解决这个问题的特性,但我并不过分乐观。无论如何,我认为我们不会错过太多,我认为std::is_convertible导致构造等同于make_unique<T>(foo, bar)的事实非常直观;特别是考虑到T(foo, bar)非常不同,只有在make_unique<T>({ foo, bar })foo具有相同类型时才有意义。

因此对于通用代码我只使用大括号进行值初始化(例如barT t {};),这非常方便,我认为优于C ++ 03方式T t = {};否则它是直接初始化语法(即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>