103

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

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

说到模板代码,改变语法会导致不同的含义,所以走对路是必不可少的。

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

4

3 回答 3

68

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

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

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

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

于 2012-04-02T13:38:26.810 回答
28

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

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

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

于 2012-04-02T13:20:18.597 回答
17

在通用代码(即模板)之外,您可以(而且我确实)在任何地方都使用大括号。一个优点是它可以在任何地方工作,例如甚至对于类内初始化:

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 };orT 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 if Tis egint的向量,或者一个大小为 20 if Tis的向量std::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 {};or 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

于 2012-04-02T15:30:35.750 回答