C ++ 0x统一初始化“奇怪”

时间:2011-08-11 19:46:40

标签: c++ c++11 uniform-initialization

和许多人一样,我对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 个答案:

答案 0 :(得分:26)

  

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

首先,大括号语法可以避免烦恼的解析:

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

在C ++ 03中,您最接近的是T x((T()));T x = T();,这两者都需要T才能拥有可访问的复制构造函数。

答案 1 :(得分: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});

它只调用T的构造函数作为参数。{1, 2, 3}。它没有做任何奇特的事情,比如如果T是数组则初始化数组,或者如果T是聚合结构/类,则初始化struct成员。如果T不是类,则该声明无效。但是如果T碰巧有副本或移动构造函数,那么它可以依次使用该构造函数通过复制列表初始化构造临时T并将复制/移动构造函数引用参数绑定到该临时。我相信你不会经常在真实的代码中使用这种形式。


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

  

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

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

     
      
  • 通过调用构造函数构建(“ctor-call”)
  •   
  • 通过转移价值构建(“转换”)
  •   
     

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

稍后,他们找到了

  

请注意,因为我们会在

中处理{ ... }
X x = { ... };
     

作为单个值,它不等于

X x{ ... };
     

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

C ++ 0x FDIS中规定的规则与该论文中提出的规则略有不同,但该论文中提出的基本原理在C ++ 0x FDIS中保留并实现。

答案 2 :(得分: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" } };

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

答案 3 :(得分: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;
}