12.7 构造与析构
1. 非平凡类在构造函数执行前和析构函数执行后,访问任何非静态数据成员或其基类都会导致未定义的行为。【例:
struct X { int i; };
struct Y : X { };
struct A { int a; };
struct B : public A { int j; Y y; };
extern B bobj;
B* pb = &bobj; // OK
int* p1 = &bobj.a; // 未定义 , 在 bobj 构造之前就访问其基类子对象了
int* p2 = &bobj.y.i; // 未定义 , 在 bobj 构造之前就访问其非静态数据成员的成员了
A* pa = &bobj; // 未定义 , 在 bobj 构造之前就试图将其向上转换为基类类型
B bobj; // 定义 bobj
extern X xobj;
int* p3 = &xobj.i; // OK, X 是平凡类
X xobj;
另外一个例子:
struct W { int j; };
struct X : public virtual W { };
struct Y {
int *p;
X x;
Y() : p(&x.j) // 未定义 , x 尚未创建 【按:x 是数据成员,在p 后声明,因此会在p 之后初始化(构造);而p 初始化的时候访问了x ,所以是未定义的。】
{ }
};
】
2. 如果想显式或隐式地将 X 的指针(左值)转化成 X 的直接或间接基类 B 的指针(引用),那么, X 及其继承于 B 的基类(不论直接还是间接)都必须已经开始构造,并且这些类的析构还没完成;否则该转换会导致未定义的行为。想形成一个对象 obj 的直接非静态成员的指针, obj 的构造必须已经开始并且析构还没结束;否则指针值的计算(或成员的访问)会导致未定义的行为。【例:
struct A { };
struct B : virtual A { };
struct C : B { };
struct D : virtual A { D(A*); };
struct X { X(A*); };
struct E : C, D, X {
E() : D(this), // 未定义 : 从 E* 向上转成 A* ,(如果可以转的话)会使用 E* → D* → A* 这样的路径,但是 D 尚未创建!
// D((C*)this), // 这样就允许了, E* → C* 允许,因为 E() 开始了 【按:且A,B,C 都已经创建,尚未析构,满足条件】; C* → A* 允许,因为 C 已经完全创建
X(this) // 允许 : 此时 A,B,C,D 都已经完全创建
{ }
};
3. 成员函数(包括虚函数)可以在构造和析构的过程中被调用。当在构造函数(包括构造初始化器)或析构函数中直接或间接调用虚函数,并且调用函数的对象正处在构造和析构过程中,实际调用的函数是本类或基类的那个虚函数,而不是覆盖了该虚函数的派生类的那个虚函数。如果被调用的虚函数中调用了另一个成员函数,这个成员函数是用显式的成员访问语法形式调用的,但这种语法形式中调用者的静态类型不是构造函数或析构函数所在的类、也不是其基类的时候,结果是未定义的。【例如:
class V {
public:
virtual void f();
virtual void g();
};
class A : public virtual V {
public:
virtual void f();
};
class B : public virtual V {
public:
virtual void g();
B(V*, A*);
};
class D : public A, B {
public:
virtual void f();
virtual void g();
D() : B((A*)this, this) { }
};
B::B(V* v, A* a) {
f(); // 调用 V::f , not A::f
g(); // 调用 B::g , not D::g
v->g(); // v 是 B 的基类 , 调用合法 , 调用 B::g
a->f(); // 未定义的行为 , a 的类型不是 B 的基类
}
4. 构造或析构的过程中可以使用 typeid 操作符。当在构造或析构函数(或其调用的函数)中使用 typeid 的时候,如果 typeid 的操作数是这个正在构建的对象时,结果是构造或析构函数所在类的类型信息。如果 typeid 的操作数是这个正在构建的对象,其静态类型既不是构造或析构函数所在的类,又不是其基类,那么结果是未定义的。
5. 构造或析构的过程中可以使用 dynamic_cast 操作符。当在构造或析构函数(或其调用的函数)中使用 dynamic_cast 的时候,如果 dynamic_cast 的操作数是这个正在构建的对象时,该对象被认为是该类的最终继承对象。如果 dynamic_cast 的操作数是这个正在构建的对象或其指针,但其静态类型既不是构造或析构函数所在的类,又不是其基类(或指针),那么结果是未定义的。【例:
class V {
public:
virtual void f();
};
class A : public virtual V { };
class B : public virtual V {
public:
B(V*, A*);
};
class D : public A, B {
public:
D() : B((A*)this, this) { }
};
B::B(V* v, A* a) {
typeid(*this); // B 的 type_info
typeid(*v); // 合法 : *v 的类型是 V, B 的基类;生成 B 的 type_info
typeid(*a); // 未定义的行为 : A 不是 B 的基类
dynamic_cast<B*>(v); // 合法 : v 是 V*, B 的基类 V ;生成 B*
dynamic_cast<B*>(a); // 未定义的行为, a 是 A* 类型, A 不是 B 的基类
}