使用 () 或 {} 在现代 C++(C++11 及更高版本)中正确初始化变量?

Properly initialising variables in modern C++ (C++11 and above), using () or {}?

C++ reference pages 说 () 用于值初始化,{} 用于值和聚合和列表初始化。那么,如果我只想进行值初始化,我应该使用哪一个? () 要么 {}?我问是因为在 Bjarne 自己的书 "A Tour of C++" 中,他似乎更喜欢使用 {},即使是对于值初始化(例如第 6 和 7 页),所以我认为总是使用{},甚至用于值初始化。 但是,最近被下面的bug咬得很厉害。考虑以下代码。

auto p = std::make_shared<int>(3);
auto q{ p };
auto r(p);

现在根据编译器 (Visual Studio 2013),q 的类型为 std::initializer_list<std::shared_ptr<int>>,这不是我想要的。 q 的实际意图实际上是 r 的意思,即 std::shared_ptr<int>。所以在这种情况下,我应该 而不是 使用 {} 进行值初始化,而是使用 ()。鉴于此,为什么 Bjarne 在他的书中似乎仍然更喜欢使用 {} 进行值初始化?例如,他在第 6 页底部使用 double d2{2.3}

为了明确回答我的问题,什么时候应该使用 () 什么时候应该使用 {}?这是语法正确性问题还是良好的编程习惯问题?

哦,呃,如果可能的话请用简单的英语。

编辑: 看来我对 value initialization 有点误解(请参阅下面的答案)。然而,上述问题仍然存在。

这是我的看法。

当使用 auto 作为类型说明符时,使用起来更简洁:

auto q = p;   // Type of q is same as type of p
auto r = {p}; // Type of r is std::initializer_list<...>

使用显式类型说明符时,最好使用{}而不是()

int a{};   // Value initialized to 0
int b();   // Declares a function (the most vexing parse)

可以使用

int a = 0; // Value initialized to 0

但是,表格

int a{};

也可用于值初始化用户定义类型的对象。例如

struct Foo
{
   int a;
   double b;
};


Foo f1 = 0;  // Not allowed.
Foo f1{};    // Zero initialized.

首先,似乎术语混淆了。您拥有的是 而不是 值初始化。当您不提供任何显式初始化参数时,就会发生值初始化。 int x; 使用默认初始化,x 的值将是未指定的。 int x{}; 使用值初始化,x 将是 0int x(); 声明了一个函数——这就是为什么 {} 更适合于值初始化。

您显示的代码未使用值初始化。使用auto,最安全的方法是使用复制初始化:

auto q = p;

Scott Meyers 在他的书中对两种初始化方法之间的区别进行了相当多的阐述 Effective Modern C++

他这样总结这两种方法:

Most developers end up choosing one kind of delimiter as a default, using the other only when they have to. Braces-by-default folks are attracted by their unrivaled breadth of applicability, their prohibition of narrowing conversions, and their immunity to C++’s most vexing parse. Such folks understand that in some cases (e.g., creation of a std::vector with a given size and initial element value), parentheses are required. On the other hand, the go-parentheses-go crowd embraces parentheses as their default argument delimiter. They’re attracted to its consistency with the C++98 syntactic tradition, its avoidance of the auto-deduced-a-std::initializer_list problem, and the knowledge that their object creation calls won’t be inadvertently waylaid by std::initializer_list constructors. They concede that sometimes only braces will do (e.g., when creating a container with particular values). There’s no consensus that either approach is better than the other, so my advice is to pick one and apply it consistently.

{}如果为空则为值初始化,否则为list/aggregate初始化。

来自草稿,7.1.6.4 自动说明符,7/...示例

auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>

规则在这里解释起来有点复杂(甚至很难从源代码中阅读!)。

还有一个重要区别:大括号初始值设定项要求给定类型实际上可以容纳给定值。换句话说,它禁止缩小值,如舍入或截断。

int a(2.3); // ok? a will hold the value 2, no error, maybe compiler warning
uint8_t c(256); // ok? the compiler should warn about something fishy going on

相对于大括号初始化

int A{2.3}; // compiler error, because int can NOT hold a floating point value
double B{2.3}; // ok, double can hold this value
uint8_t C{256}; // compiler error, because 8bit is not wide enough for this number

特别是在使用模板的泛型编程中,因此您应该使用大括号初始化来避免当基础类型对您的输入值做出意想不到的事情时出现令人讨厌的意外情况。

Herb Sutter 似乎正在制作一个 argument in CppCon 2014 (39:25 into the talk) 用于使用自动和大括号初始化器,如下所示:

auto x = MyType { initializers };

无论何时你想要强制转换类型,以实现定义中从左到右的一致性:

  • 类型推导:auto x = getSomething()
  • 类型强制:auto x = MyType { blah }
  • 用户定义的文字auto x = "Hello, world."s
  • 函数声明:auto f { some; commands; } -> MyType
  • 命名的 Lambda:using auto f = [=]( { some; commands; } -> MyType
  • C++11 风格的类型定义:using AnotherType = SomeTemplate<MyTemplateArg>

Scott Mayers 刚刚发布了一篇相关的博客文章 Thoughts on the Vagaries of C++ Initialization。看来C++要实现真正统一的初始化语法还有一段路要走。