17

像许多人一样,我对C++0x感到非常兴奋。我尝试在新项目中学习和使用新功能,这样我就可以编写出最好的、最易于维护的代码。

不用说,我喜欢新初始化器背后的想法。所以我正在看它们,这些对我来说很有意义:

T x = { 1, 2, 3 }; // like a struct or native array
T x({1, 2, 3});    // copy construct something like an "object literal" in other languages... cool!
return {1, 2, 3};  // similar to above, but returning it, even cooler!

对我来说没有意义的是:

T x{1, 2, 3};

只是感觉……很奇怪。我不确定人们想使用什么语法来模仿它,它看起来并不“正确”。

这种语法背后的设计/思想是什么?

似乎有所不同的唯一示例是这样的:

std::vector<int> the_vec{4};

这将调用初始化列表构造函数,但为什么不直接写这个:

std::vector<int> the_vec = {4};

做每个人都已经习惯的事情吗?

4

4 回答 4

26

这种语法背后的设计/思想是什么?

一方面,大括号语法可以避免令人烦恼的解析:

T x(); // function declaration
T x{}; // value-initialized object of type 'T' named 'x'

在 C++03 中,最接近 this 的是T x((T()));or T x = T();,两者都需要T有一个可访问的复制构造函数。

于 2011-08-11T19:56:37.960 回答
19

首先,您确实有两种变体:

T x = { 1, 2, 3 };
T x{1, 2, 3};

这两个实际上是在执行相同的初始化,除了第一个在选择explicit构造函数时无效。否则它们是相同的。第一个称为“复制列表初始化”,第二个称为“直接列表初始化”。

这个概念是带有 的表单=正在分配一个“复合值” - 一个由 3 个整数组成的值。x它使用该值进行初始化。对于这样的初始化, explicit应该只允许非构造函数。x{1, 2, 3}(不带等号)的概念是您使用3 个值初始化变量 - 概念上不是复合值,而是您碰巧一次给出的 3 个单独的值。您可以说这是该术语最一般意义上的“构造函数调用”。

您展示的另一个初始化确实与上述两个完全不同:

T x({1, 2, 3});

它只调用Twith的构造{1, 2, 3}函数作为参数。它不做任何花哨的事情,比如如果是数组则初始化数组或如果是聚合结构/类T则初始化结构成员。T如果T不是类,则该声明无效。但是如果T碰巧有一个复制或移动构造函数,那么它可以反过来使用该构造函数T通过复制列表初始化构造一个临时对象,并将复制/移动构造函数引用参数绑定到该临时对象。我相信您在实际代码中不会经常需要这种形式。


所有这些都记录在初始化列表的委员会提案文件中。在这种情况下,您想查看Initializer Lists — Alternative Mechanism and Rationale,在“程序员对初始化类型的看法”部分:

我们观察到,意识到复制初始化和直接初始化之间区别的专业程序员经常错误地认为前者效率低于后者。(实际上,当两个初始化都有意义时,它们同样有效。)

相比之下,我们发现以不同的方式思考这些事情更有用:

  • 通过调用构造函数进行构造(“ctor-call”)
  • 通过传递一个值来构造(“转换”)

(碰巧,前者对应于“直接初始化”,后者对应于“复制初始化”,但标准的条款对程序员没有帮助。)

后来,他们发现

请注意,由于我们将{ ... }in

X x = { ... };

作为单个值,它不等于

X x{ ... };

其中{ ... }是构造函数调用的参数列表(我们强调它是因为它与 N2531 不同)。

C++0x FDIS 中列出的规则与该论文中提出的规则略有不同,但该论文中提出的基本原理在 C++0x FDIS 中得到保留和实施。

于 2011-08-11T20:15:53.187 回答
4

从理论的角度来看,给出的答案很好,但也许一些实际的例子也会有用。通过统一初始化,现在可以编写以前根本不可能的构造。例如:

  • 初始化成员数组。

  • 全局常量容器(例如地图)。

以机智:

class Foo
{
  int data[3];
public:
  Foo() : data{1,2,3} { }
};

这里我们可以直接初始化成员数组,不需要赋值(考虑默认构造不可用的情况)。

const std::map<int, std::string> labels {
  { 1 , "Open" },
  { 2 , "Close" },
  { 3 , "Reboot" } };

有时只读全局查找对象很有用,但如果没有统一初始化,就无法用数据填充它。

于 2011-08-11T21:27:37.570 回答
1

我认为语法一致性在泛型编程中非常重要。例如考虑,

#include <utility>
#include <tuple>
#include <vector>

template <class T>
struct Uniform {
  T t;
  Uniform() : t{10, 12} {}
};

int main(void)
{
  Uniform<std::pair<int, int>> p;
  Uniform<std::tuple<int, int>> t;
  Uniform<int [2]> a;
  Uniform<std::vector<int>> v; // Uses initializer_list

  return 0;
}
于 2011-08-14T23:04:45.780 回答