class A { public: int x; };
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
D d;
d.x = 1; // 正确,只有一份 A::x
cpp复制代码
#include <iostream>
using namespace std;
// M 是成员对象
class M {
public:
M() { cout << "M" << endl; }
~M() { cout << "M" << endl; }
};
// A 是虚基类
class A {
public:
A(int val) { cout << "A" << endl; }
~A() { cout << "A" << endl; }
};
// B 和 C 虚继承 A
class B : virtual public A {
public:
B() : A(1) { cout << "B" << endl; } // 写 A(1),作为中间类时被忽略
~B() { cout << "B" << endl; }
};
class C : virtual public A {
public:
C() : A(2) { cout << "C" << endl; } // 写 A(2),作为中间类时被忽略
~C() { cout << "C" << endl; }
};
// D 是最终派生类,继承 B 和 C,里面有一个成员 M
class D : public B, public C {
public:
M m;
D() : A(3), B(), C() { cout << "D" << endl; } // D 写 A(3),真正生效的就是这个
~D() { cout << "D" << endl; }
};
int main() {
cout << "--- 实例化 D 对象 ---" << endl;
{
D d;
// 预期构造输出:A B C M D
}
cout << "--- D 对象销毁结束 ---" << endl;
cout << endl;
cout << "--- 单独实例化 B 对象 ---" << endl;
{
B b;
// 预期构造输出:A B
}
cout << "--- B 对象销毁结束 ---" << endl;
return 0;
}
运行结果是
cpp复制代码
--- 实例化 D 对象 ---
A
B
C
M
D
D
M
C
B
A
--- D 对象销毁结束 ---
--- 单独实例化 B 对象 ---
A
B
B
A
--- B 对象销毁结束 ---
例子回顾一下:A 是虚基类,B 和 C 虚继承 A,D 继承 B 和 C,D 里还有一个成员对象 M。D 的初始化列表写 A(3),B 的列表写 A(1),C 的列表写 A(2)。
第一条,为什么输出是 A B C M D 。构造 D 时先构造虚基类 A,用的参数是 3,因为 D 是最终派生类,只有它给 A 的参数算数。然后按继承声明顺序构造普通基类 B 和 C,各自打印自己。接着构造 D 的成员 M。最后执行 D 自己的构造函数体。所以顺序就是虚基类、普通基类、成员、自身。
第二条,为什么 B 和 C 里的 A(1) 和 A(2) 被忽略。因为 B 和 C 在这里是中间类,不是最终被实例化的类。C++ 规定虚基类只由最终派生类初始化 ,**中间类在初始化列表里写虚基类会被编译器跳过。**这样保证 A 只构造一次,不会出现两份 A。
第三条,为什么单独实例化 B 时 A(1) 又生效了。写 B b 的时候,B 自己就是最终派生类,没有更下层的类来负责 A,所以轮到 B 初始化 A,A(1) 就用上了,输出变成 A B。这说明中间类还是最终派生类,取决于当前实例化的是谁。
第四条,成员 M 的构造位置。M 排在普通基类之后、自身构造体之前。而且 M 按它在类里声明的顺序构造,不是按初始化列表里写的顺序。如果 D 里有多个成员,谁先声明谁先构造。
第五条,**析构为什么是 D M C B A。**析构严格反着来:先执行 D 自身析构体打印 D,再析构成员 M 打印 M,再析构普通基类,按构造逆序先 C 后 B,最后析构虚基类 A 打印 A。A 只析构一次。
第六条,虚基类 A 的构造和析构都只发生一次,这是虚继承的核心效果。对比非虚继承,如果不写 virtual,D 里会有两份 A,A 会构造两次、析构两次,访问 x 还会有二义性。
第七条,各角色的对应关系。A 是虚基类,负责被共享、只一份。B 和 C 是普通基类,同时在这里是中间类。M 是成员对象。D 的构造函数花括号和析构函数花括号是自身构造体和自身析构体。D 是最终派生类,负责虚基类的初始化和析构。