12.6 初始化
1. 当类的对象(或对象数组,可能是 cv 修饰的)声明时没有指定初始化器,或是初始化器是 () 的形式,该对象以 8.5 规定的方式初始化。没指定初始化器的时候执行默认初始化,初始化器是()的时候执行值初始化 。
2. 类的对象(或对象数组)可以被显式初始化,参见 12.6.1 和 12.6.2 。
3. 当对象数组被初始化(显式或隐式)时,按照下标的顺序调用数组每个元素的构造函数。【注:析构函数的顺序相反。】
12.6.1 显式初始化
1. 类的对象可以用括号括起来的表达式列表来初始化,这个表达式列表被解释为初始化该对象时调用的构造函数的参数列表。还可以使用一个赋值表达式作为 = 形式的初始化器。直接初始化或拷贝初始化的语义在这里仍适用;参见 8.5 。【例:
class complex {
// ...
public:
complex();
complex(double);
complex(double,double);
// ...
};
complex sqrt(complex,complex);
complex a(1); // 调用 complex(double) 来初始化
complex b = a; // 将 a 拷贝给 b ,调用隐式定义的拷贝构造函数
complex c = complex(1,2); // 用 complex(double,double) 创建一个临时对象,将其直接拷贝给 c 。 【按:这里没有调用隐式定义的拷贝构造函数,是为了提高效率的原因:在&c 处进行了直接初始化(C++ 标准允许编译器这么做);但语义上仍然要调用拷贝构造函数,所以该隐式定义的拷贝构造函数必须有可访问权限。】
complex d = sqrt(b,c); // 调用 sqrt(complex,complex) 生成临时对象,将其拷贝给 d
complex e; // 调用 complex() 初始化
complex f = 3; // 调用 complex(double) 创建一个 complex(3) 临时对象,将其拷贝给 f
complex g = { 1, 2 }; // 错误,需要相应的构造函数
】【注意:重载赋值操作符对初始化没影响。】
2. 当一个包含类对象的聚集(类或者数组)使用 { 初始化器列表 } 的形式初始化的时候,每个成员都执行对应的拷贝初始化。如果初始化列表中的初始化器比聚集的成员个数少,每个没有被显式初始化的成员必须被值初始化。【注: 8.5.1 描述了初始化列表中的赋值表达式是如何与被初始化的成员配对的。】【例如:
complex v[6] = { 1,complex(1,2),complex(),2 };
这里, v[0] 和 v[3] 调用 complex::complex(double) 来初始化, v[1] 调用 complex::complex(double,double) 来初始化, v[2] 、 v[4] 和 v[5] 调用 complex::complex() 来初始化。
另一个例子:
class X {
public:
int i;
float f;
complex c;
} x = { 99, 88.8, 77.7 };
这里, x.i 初始化为 99 , x.f 初始化为 88.8 , x.c 调用 complex::complex(double) 拷贝初始化。【注:初始化聚集对象时,初始化器列表中的花括号可以省略,甚至在聚集有 class 类型的成员且自定义了类型转换函数的情况下也可以省略。】
3. 【注:如果类 T 没有默认构造函数,声明 T 类型的对象时不带初始化器是非法的。】
4. 【注:静态对象的初始化顺序在 3.6.2 和 6.7 中有描述。】
12.6.2 初始化基类和成员
1. 在类的构造函数定义语句中,直接基类子对象、虚基类子对象 及非静态数据成员的初始化器可以通过一个构造初始化器来指定,形式如下:
ctor-initializer:
: mem-initializer-list
mem-initializer-list:
mem-initializer
mem-initializer , mem-initializer-list
mem-initializer:
mem-initializer-id ( expression-list (可选) )
mem-initializer-id:
:: (可选) nested-name-specifier (可选) class-name
identifier
2. mem-initializer-id 中的名字在构造函数所在类域中查找,如果没找到,就在包含构造函数的定义的域中查找。【注:如果该类包含一个与直接基类或虚基类名字相同的成员,用来命名成员或基类的 mem-initializer-id 指的是成员的名字;被隐藏的基类名字应该用带限定符的名字。】 mem-initializer-id 只能命名该类的非静态数据成员、直接基类或虚基类,其他情况都是非法的。可以使用 mem-initializer-list 来初始化基类,可以用任何指代该基类的名字。【例:
struct A { A(); };
typedef A global_A;
struct B { };
struct C: public A, public B { C(); };
C::C(): global_A() { } // mem-initializer 初始化基类 A
】
如果 mem-initializer-id 既可以指代直接非虚基类,也可以指代它的虚基类,从而造成二义性,那么 mem-initializer 就是非法的。【例如:
struct A {A();}
struct B: public virtual A{}
struct C: public A, public B {C();}
C::C() : A() {} // 非法,到底是哪个 A?
】
如果匿名 union 是类的一个成员,该类的构造初始化器可以用来初始化这个匿名 union 的成员。如果构造初始化器中为同一个成员(或同一个直接基类,或同一个虚基类)指定了多个 mem-initializer ,构造初始化器就是非法的。
3. mem-initializer 中的 expression-list 根据 mem-initializer-id 来初始化基类或非静态数据成员子对象, mem-initializer 的语义如下:
a) 如果 mem-initializer 的 expression-list 省略,基类或成员子对象被值初始化;
b) 否则, mem-initializer-id 指示的子对象被用 expression-list 作为初始化器直接初始化。
【例:
struct B1 { B1(int); /* ... */ };
struct B2 { B2(int); /* ... */ };
struct D : B1, B2 {
D(int);
B1 b;
const int c;
};
D::D(int a) : B2(a+1), B1(a+2), c(a+3), b(a+4)
{ /* ... */ }
D d(10);
】
在每个基类和成员初始化之后都有一个序列点 。 mem-initializer 的 expression-list 的计算被看做是对应的基类或成员初始化过程的一部分。
4. 如果给定的非静态数据成员或基类不是用 mem-initializer-id 命名的(包括由于构造函数没有构造初始化器而没有 mem-initializer-id 的情况),那么:
a) 如果实体是非静态数据成员或基类,并且实体的类是个非平凡类,该实体被默认初始化;如果实体是 const 类的非静态数据成员,实体类必须有一个用户自定义的构造函数。
b) 否则,实体不会被初始化。如果实体是 const 或引用类型,或包含 const 类型成员的平凡类,程序就是非法的。
类 X 的构造函数调用结束之后,如果 X 有个成员没在 mem-initializer 中指定,也没有默认初始化,也没有值初始化,在构造函数体执行过程中也没有赋值,它的值是不确定的。
5. 初始化必须按照如下顺序执行:
a) 首先,只对最终继承类的构造函数来说,各虚基类必须按照它们在继承关系形成的有向无环图中出现的顺序:深度优先,从左到右遍历,来进行初始化。“从左到右”指的是在继承列表中出现的顺序;
b) 其次,直接继承类必须按照它们在继承列表中出现的顺序来初始化;(不管成员初始化列表中的顺序)
c) 再次,非静态数据成员必须按照它们声明时的顺序来初始化;(也不管成员初始化列表中的顺序)
d) 最后,执行构造函数体
【注:声明顺序被授权保证基类和成员子对象按照初始化相反的顺序销毁。】
6. 所有虚基类子对象都被最终继承类的构造函数初始化。如果最终继承类的构造函数没有在成员初始化列表中指定虚基类 V ,那么 V 的默认构造函数会被调用来完成其初始化。如果 V 没有可访问的默认构造函数,初始化语法是非法的。命名虚基类的成员初始化器在执行除最终继承类之外的任何类的构造函数的时候都会被忽略掉。【例:
class V {
public:
V();
V(int);
// ...
};
class A : public virtual V {
public:
A();
A(int);
// ...
};
class B : public virtual V {
public:
B();
B(int);
// ...
};
class C : public A, public B, private virtual V {
public:
C();
C(int);
// ...
};
A::A(int i) : V(i) { /* ... */ }
B::B(int i) { /* ... */ }
C::C(int i) { /* ... */ }
V v(1); // use V(int)
A a(2); // use V(int)
B b(3); // use V()
C c(4); // use V()
7. 成员初始化器的表达式列表中的名字在指定初始化器的构造函数域中进行计算。【例:
class X {
int a;
int b;
int i;
int j;
public:
const int& r;
X(int i): r(a), b(i), i(i), j(this->i) {}
};
将 X::r 初始化为 X::a ,将 X::b 初始化为构造函数的参数 i , X::i 初始化为构造函数的参数 i , X::j 初始化为 X::i ,每次创建 X 的对象时都会发生上述行为。】
【注:由于成员初始化器的表达式列表中的名字在指定初始化器的构造函数域中进行计算, this 指针可以用来指示正被初始化的对象。】
8. 成员函数(包括虚函数)在函数构造的过程中可以调用。同样,构造中的对象可以作为 typeid 或 dynamic_cast 的操作数。然而,如果在所有基类成员初始化器完成之前就在构造初始化器中(或者被该构造初始化器调用的函数中)执行这些操作,结果是未定义的。【例:
class A {
public:
A(int);
};
class B : public A {
int j;
public:
int f();
B() : A(f()), // 未定义 : 调用成员函数 f ,但此时基类子对象 A 还没初始化
j(f()) { } // 没问题 : 此时所有基类都已初始化
};
class C {
public:
C(int);
};
class D : public B, C {
int i;
public:
D() : C(f()),// 未定义 : 调用成员函数 f ,但此时基类子对象 C 还没初始化(虽然 B 已初始化)
i(f()) {} // 没问题 : 此时所有基类都已初始化
};
】
9. 【注: 12.7 描述了在没问题的情况下对象构造过程中调用虚函数、 typeid 、 dynamic_cast 的结果;即描述了构造过程中的多态行为。】