为什么C++的初始化规则这么复杂?

关注者
160
被浏览
105,487

14 个回答

首先梳理一下目前C++的初始化有哪几种。

按被初始化的变量的类型分:

  1. 引用
  2. 字符数组
  3. 聚合类型(aggregate type;数组,没有构造函数的struct和union ←_← 一般这种类型不声明为class)
  4. 类类型(struct、class、union)
  5. 内建对象类型和enum

这些虽然不算简单(尤其是考虑类型转换函数 operator T ),但毕竟是和类型对应的。不同类型有不同的初始化规则也算正常。

按初始化的语法分:

  1. 默认初始化( default-initialization ,例如 T t;
  2. 直接(非列表)初始化( direct-(non-list-)initialization ,例如 T t(args...);
  3. 复制(非列表)初始化( copy-(non-list-)initialization ,例如 T t = init;
  4. 直接列表初始化( direct-list-initialization ,例如 T t{ args... };
  5. 复制列表初始化( copy-list-initialization ,例如 T t = { args... };

这就麻烦多了。同一个场合常常有多种初始化语法可以选择,甚至各种语法常常有相同的效果,但是它们有时又有不同的语义,所以不能无脑选择。这显然是过度复杂了。

这几种语法中,#1、#3和最初的#5是从C语言抄的。在C语言里,三者的分工算是比较明确:#1用于没有初始值的情况,#3用于类似赋值的初始化,#5用于其他情况(主要是逐个初始化结构体成员和数组元素,但有 { 0 } 这个对所有对象都能用的“万能初始化”)。

#2大概是C++最初就有的。这种圆括号语法似乎看起来很符合直觉,比如 new Complex(1, 0) Complex(1, 0) 能让人一看就想到构造函数调用(虽然有严重的语法二义性问题)。

C++标准化以前,#2大概已经可以用于没有构造函数的类型,包含了#1和#3的功能。

C++标准化时,发现如果#2和#3语义相同,会导致

// 假设直接初始化和复制初始化具有相同语义
std::vector<int> a(42); // 如果这是合法的...