C++的花括号初始化

作者:, 发表于

花括号初始化是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<int> x = 1; // not valid because copy constructor not exist

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

std::atomic<int> x1(1);   // valid
std::atomic<int> 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.


上一篇:GCC LD对依赖库的输入顺序敏感2016年6月5日
LD在链接生成目标文件时,会从左到有扫描输入的依赖库,当依赖库之间也有依赖关系时,必须将”依赖别人的库”放在“被别人依赖的库”的前面。

下一篇:计算星期几2016年7月1日
int day_of_week(int y, int m, int d) /* 0 = Sunday */ { static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; y -= m < 3; return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; }


  • 支持使用微薄、微信和QQ的账户登陆进行评论。由各自网站直接认证,不会泄露你的密码。
  • 登陆后可选择分享评论到所绑定的社交网络,如微薄、人人和QQ空间。
  • 评论提交后无法修改。如需修改,请删除原评论再重新提交。
  • 评论支持LaTeX代码,行内公式请用\(a+b=c\),行间公式请用\[a+b=c\]。公式只支持英文字符。