C++ 的花括号初始化

作者: , 共 2987 字

花括号初始化是C++11引入的一种初始化方法。

1. 花括号初始化的语法

1.1. 直接初始化

T object { arg1, arg2, ... };   
T { arg1, arg2, ... }; 
new T { arg1, arg2, ... }
class Class { T member { arg1, arg2, ... }; };   
Class::Class() : member{arg1, arg2, ...} {...  }

1.2. 复制初始化

T object = {arg1, arg2, ...};
function( { arg1, arg2, ... } ) ;
return { arg1, arg2, ... } ;  
object[ { arg1, arg2, ... } ] ;
object = { arg1, arg2, ... } ;  
U( { arg1, arg2, ... } )    
class Class { T member = { arg1, arg2, ... }; };

使用花括号初始化时,编译必须加上--std=c++11选项。

2. 花括号初始化的规则

C++的复杂之处在这里体现的淋漓尽致,当编译器遇到花括号时,规则见

http://en.cppreference.com/w/cpp/language/list_initialization

规则很长。除去那些简单的情况,约有三种情况:

2.1. 聚合初始化 aggregate initialization

当类型是一个聚合结构( aggragates )时,花括号将进行聚合初始化。

聚合结构是指一个数组( array )或者一个满足下面条件的结果:

  • 所有非静态的成员都是共有的( public )
  • 没有自定义的初始化函数。
  • 没有虚拟成员函数
  • 继承基于 public

聚合结构可以简单理解为 c 语言里的普通结构。聚合初始化时,结构用花括号里的数据依次填充成员变量。当然,具体的规则要复杂很多,详情可见:

http://en.cppreference.com/w/cpp/language/aggregate_initialization

例子:

#include <string>
#include <array>
struct S {
    int x;
    struct Foo {
        int i;
        int j;
        int a[3];
    } b;
};

int main()
{
    S s1 = { 1, { 2, 3, {4, 5, 6} } };
    S s2 = { 1, 2, 3, 4, 5, 6}; // same, but with brace elision
    S s3{1, {2, 3, {4, 5, 6} } }; // same, using direct-list-initialization syntax
}

2.2. 初始化列表初始化 initializer list initialization

这里指 C++编译器遇到花括号列表时,将其作为一个std::initializer_list的对象,当初始化对象存在接受std::initializer_list为参数的构造函数时,编译器将调用该构造函数直接进行初始化。

std::initializer_list的文档:

http://en.cppreference.com/w/cpp/utility/initializer_list

很多std容器如vectormapset等都有接受std::initializer_list的构造函数,因此可以像普通数组一样使用花括号列表初始化。

2.3. 匹配构造函数参数

当上面两种情况不存在时,编译器将依次匹配初始化对象的所有初始化函数,调取其中参数匹配的。如下例:

std::vector<std::string> strs1{100, "abc"}; // it's the same with below
std::vector<std::string> strs2(100, "abc"};

还有个特殊的情况,是匹配构造函数参数和上面的初始化列表初始化的的结合。比如下面这种写法:

std::vector<int> x{{1, 2}};

编译器遇到最外层的花括号时,开始匹配构造函数参数,内层的花括号表示参数为初始化列表。

2.4. 复制初始化 copy initialized

上面谈到的三种情况都是直接初始化( direct initialized ),对应的是上面第一部分的直接初始化的语法。而对于第一部分中的复制初始化的语法,将采取复制初始化。

字面上而言,复制初始化将先使用上面三种直接初始化方法之一初始化一个临时变量,然后调用copymove函数将临时对象复制到需初始化的对象上。

但实际操作中,编译器会使用一种叫做copy elision的技术。简而言之就是已知临时变量必然没有用处,那我也不需要建立临时变量,直接用直接初始化方法好了。

因此,在类似std::vector<int> x = {1, 2, 3}这样的语法中,最后生成的二进制代码将和std::vector<int> x{1, 2, 3}一致,它们的效率一模一样。

但复制初始化和直接初始化在编译期还是有区别,区别在于编译器会检查复制构造函数的存在性和可访问性,虽然编译器最后并没有用到它。

比如std::atomic禁用了复制构造函数(一般设置为=delete或者把复制构造函数设置为 private ),下面这种初始化就无法编译:

std::atomic&lt;int&gt; x = 1; // not valid because copy constructor not exist

你必须使用括号或者花括号:

std::atomic&lt;int&gt; x1(1);   // valid
std::atomic&lt;int&gt; x2{1};   // valid, the same as x2(1);

3. 花括号的良好编程习惯

我个人坚持的习惯有两个:

  • 尽量使用复制初始化。即std::vector<int> x = {1, 2, 3}要好于std::vector<int> x{1, 2, 3}。它们两者的效率无差异,但前者的可读性要高于后者。
  • 当调用普通构造函数时,尽量不要使用花括号,而要用小括号代替,以免引起歧义。比如不要用std::vector<std::string> strs{100, "abc"},而要用std::vector<std::string> strs(100, "abc")

Q. E. D.

类似文章:
编程 » Excel, VBA
无意中发现一个 Excel VBA 对待参数的一个"不正常"现象。这种处理方式可能无意中导致程序结果错误,而且你很难发现你的错误所在:
编程 » C++, 编译错误
在 gcc 中,存在继承关系的模版类,子类无法直接访问父类的成员,即使该成员是protectedpublic
相似度: 0.080
编程 » C++, 算法
一个短小、高效的 C++函数,用来判断指定日期是星期几:
C++内存检查和性能分析工具 valgrind里介绍了 valgrind 的安装,以及用于效率分析 profiler 工具。valgrind 最本来的功能是内存检查。这篇文章做简单的介绍。
相似度: 0.075
最近写了一些 Matlab 程序,想起以前想过的一个东西,记录一下。
相似度: 0.073
编程 » C++
C++的浮点数转整数有四种方法,直接类型转换、round、floor、ceil。其效果如下表:
编程 » Matlab, swap, 函数包
Matlab 程序效率低下,其中一个原因就是它的参数无法引用,每次都是传值。这不但导致效率问题,要实现某些功能,也需要一些特殊的手段。比如最简单的,如果交换两个变量的值,也就是在 C/C++里的函数 void swap(int& a, int& b),在 C/C++里实现很容易,但在 Matlab 里,你会吗?
相似度: 0.060
boost是除std外最常用的 C++库,覆盖很多常用操作。目前最新的版本是1.59.0
编程 » Java, Matlab
Matlab 2008b 才开始引入 containers.Map ,这是 Matlab 唯一的数据结构(这里的数据结构是指自带一定逻辑性的数据结构,不包括普通数据类型)。如果要有其它,比如 Queue、Set 等数据结构,只能自己编写一个。File Exchange 上有不少人做过这个工作,我也写过Queue、List、Vector 的 Matlab 对象。不过 Matlad 的面向对象编程效率极低,这种方法只能用于不太注重效率的场合。解决这个问题的另外一个方法是使用 Java 对象。
编程 » Matlab, 编译器
现在比较新的电脑基本上都是 64 位的 CPU , Matlab 也是 64 位的版本,但 64 位的 Matlab 没有自带编译器,需另行安装编译器。下面是方法之一:
编程 » C++, GCC, 编译链接
LD 在链接生成目标文件时,会从左到有扫描输入的依赖库,当依赖库之间也有依赖关系时,必须将「依赖别人的库」放在「被别人依赖的库」的前面。否则会链接失败!失败的症状有:
后一篇:
编程 » C++, 算法
一个短小、高效的 C++函数,用来判断指定日期是星期几: