C++ 的花括号初始化

作者: , 共 3040 字 , 共阅读 0

花括号初始化是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

例子:

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}};

注意它的结果和下面一样,但没有歧义了。因为下面这个容易被误解为调用std::vector<int>(1, 2)的构造函数:

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.

类似文章:
编程 » Excel, VBA
无意中发现一个 Excel VBA 对待参数的一个"不正常"现象。这种处理方式可能无意中导致程序结果错误,而且你很难发现你的错误所在:
编程 » C++
假设在 C++里有一个数据结构:
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::small_vector,代码文件地址:https://github.com/facebook/folly/blob/master/folly/small_vector.h
编程 » folly, C++, 数据容器
由 Facebook 开发和维护的 C++库 Folly 提供folly::sorted_vector_setfolly::sorted_vector_map,是std::mapstd::set在小数据集上的优化版。代码见: https://github.com/facebook/folly/blob/master/folly/sorted_vector_types.h
编程 » C++, 数据容器, folly
folly::dynamic提供类似于C++的动态类型。和std::any可以容纳任意类型不一样,folly::dynamic只支持保存以下几种类型:
编程 » C++, 智能指针
理论上而言,当 C++提供了std::unique_ptr, C++的程序就不应该出现普通指针了。所有普通指针都可以用std::unique_ptr代替,避免手动删除对象。
编程 » C++, 智能指针
前面已经提到std::shared_ptr有三个缺陷:
编程 » C++, Boost, 数据容器
Boost.Intrusive 是一个很有意思的实现,里面实现了很多侵入式容器,在特定环境下,可以大大提升性能。
编程 » C++, 编译错误
在 gcc 中,存在继承关系的模版类,子类无法直接访问父类的成员,即使该成员是protectedpublic
由 Facebook 开发和维护的 C++库 Folly 提供了自旋锁的实现folly::MicroSpinLock,代码文件地址:https://github.com/facebook/folly/blob/master/folly/synchronization/MicroSpinLock.h
编程 » C++, GCC, 编译链接
LD 在链接生成目标文件时,会从左到有扫描输入的依赖库,当依赖库之间也有依赖关系时,必须将「依赖别人的库」放在「被别人依赖的库」的前面。否则会链接失败!失败的症状有:
后一篇:
编程 » C++, 算法, 代码片段
一个短小、高效的 C++函数,用来判断指定日期是星期几: