First, why use a virtual base class?
The simple point is to save space and avoid constructing the same object multiple times under multiple inheritance conditions.
// 直接使用多继承
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : public A, public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : public A, public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public C, public D, public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果如下, e的生命周期
A的构造函数调用
B的构造函数调用
C的构造函数调用
A的构造函数调用
B的构造函数调用
D的构造函数调用
A的构造函数调用
E的构造函数调用
E的析构函数调用
A的析构函数调用
D的析构函数调用
B的析构函数调用
A的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
// 使用虚拟基类后
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : virtual public A, virtual public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : virtual public A, virtual public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果,e的生命周期
A的构造函数调用
B的构造函数调用
C的构造函数调用
D的构造函数调用
E的构造函数调用
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
From this, it can be clearly seen that the construction times of the two base classes of A and B are reduced.
Next, let’s talk about their construction order, and first post the instructions on the C++ document.
Simply put,
- First initialize the virtual base pointer of the base class or object
- Initialize member objects
- call its own constructor
code:
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : public A, public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
A a;
B b;
};
class D : public A, public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
B b;
A a;
};
class E : public C, public D, public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果,只看对象e的构造函数的顺序,析构的没放上来。
// E需要C、D、B三个基类的构造
A的构造函数调用
B的构造函数调用 // E的多继承中C的多继承基类的构造
A的构造函数调用
B的构造函数调用 // C中的A、B两个成员对象的构造
C的构造函数调用 // C本身构造函数调用
A的构造函数调用
B的构造函数调用 // E的多继承中D的多继承基类的构造
B的构造函数调用
A的构造函数调用 // D中的B、A两个成员对象的构造
D的构造函数调用 // D本身构造函数调用
B的构造函数调用 // E的多继承中B的构造
E的构造函数调用 // E本身构造函数的调用
// 使用虚拟基类
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : virtual public A, virtual public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
A a;
B b;
};
class D : virtual public A, virtual public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
B b;
A a;
};
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果
A的构造函数调用
B的构造函数调用 // C中的A、B两个成员对象的构造
A的构造函数调用
B的构造函数调用
C的构造函数调用 // C中的多继承基类的构造
B的构造函数调用
A的构造函数调用 // D中的B、A两个成员对象的构造
D的构造函数调用 // 虚拟基指针初始化指向前面构造过的A、B,无需重新构造
E的构造函数调用 // 最后虚拟基B类也是指向前面创建好的B,最后调用E的构造函数即可
The order of the destructor is described in the official document first.
It is explained in two cases:
- Non-virtual base class, simply put: objects are destructed in the reverse order of construction
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : public A, public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
A a;
B b;
};
class D : public A, public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
B b;
A a;
};
class E : public C, public D, public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果
A的构造函数调用
B的构造函数调用
A的构造函数调用
B的构造函数调用
C的构造函数调用
A的构造函数调用
B的构造函数调用
B的构造函数调用
A的构造函数调用
D的构造函数调用
B的构造函数调用
E的构造函数调用
// 析构顺序,同构造顺序相反,先析构本身,然后析构成员对象,最后析构基类
E的析构函数调用
B的析构函数调用
D的析构函数调用
A的析构函数调用
B的析构函数调用
B的析构函数调用
A的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
B的析构函数调用
A的析构函数调用
- The virtual base class, the picture of the official document,
look at the code first:
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : virtual public A, virtual public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : virtual public A, virtual public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
General process:
- First of all, the left in the left refers to the first base class of the multiple inheritance declaration. The first base class of E is C, and the first base class of C is A. A does not inherit any class. This is the order: E > C > A
- Remember the last A, visit the previous C, first judge A in steps 4 and 5, whether A is in the list, if not, add to the bottom, and do nothing if it is (list: A)
- At this time, operate the access point C, find the next inherited class, find B, and B is the virtual base class. You don’t need to go back to the second step, and directly perform the judgment of steps 4 and 5. If it is not, add it to the bottom of the list (list: A > b)
- Continue to make access point C. At this time, C has no base class for class operations. Visit the previous node A of C. Remember that C, C is not a virtual base class, and it is directly added to the bottom of the list without making a judgment. (list: A > B > C)
- Then operate the access point A, the same final list as above is: A > B > C > D > E, and then destruct in the order of the list.
Let's use the code to give a few control experiments to understand
// 原实验
class A {
public:
A() { cout << "A的构造函数调用" << endl; }
~A() { cout << "A的析构函数调用" << endl; }
};
class B {
public:
B() { cout << "B的构造函数调用" << endl; }
~B() { cout << "B的析构函数调用" << endl; }
};
class C : virtual public A, virtual public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : virtual public A, virtual public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
int main()
{
E e;
}
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
// 下面的代码只展示不同部分,太多重复的看得不爽
// 实验1:C不使用虚拟继承
class C : public A, public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用 // C此时对B、A进行了析构,未使用虚拟基类会初始化,因此肯定要单独析构创建的对象,但顺序还是正确的。
B的析构函数调用
A的析构函数调用
// 实验2:改变E的C、D的生命顺序
class E : public D, public C, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
C的析构函数调用
D的析构函数调用 // 此时可以看到C、D的顺序相反,印证了算法是没错的
B的析构函数调用 // 但A、B顺序是没变的,因为我们没改变C、D的继承的声明顺序
A的析构函数调用
// 实验3:改变D的声明顺序
class D : virtual public B, virtual public A {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
// E没改
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
// 结果,没有变化,因为B、A的顺序在C时就确定了,D的声明顺序不管紧要
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
// 实验4:修改C的A、B的生命顺序
class C : virtual public B, virtual public A {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : virtual public A, virtual public B {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public C, public D, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
A的析构函数调用
B的析构函数调用 // A、B的顺序相反了,因为他们的析构调用是根据C的声明顺序来的,改变C会改变A、B。
// 实验5:修改E的C、D的声明顺序,并改变D的声明顺序
class C : virtual public A, virtual public B {
public:
C() { cout << "C的构造函数调用" << endl; }
~C() { cout << "C的析构函数调用" << endl; }
};
class D : virtual public B, virtual public A {
public:
D() { cout << "D的构造函数调用" << endl; }
~D() { cout << "D的析构函数调用" << endl; }
};
class E : public D, public C, virtual public B {
public:
E() { cout << "E的构造函数调用" << endl; }
~E() { cout << "E的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
C的析构函数调用
D的析构函数调用
A的析构函数调用
B的析构函数调用 // 调用顺序也改变了
The above is my understanding.
Official documentation link for constructors: https://docs.microsoft.com/zh-cn/cpp/cpp/constructors-cpp?view=msvc-170
Official documentation link for destructors: https://docs.microsoft.com/ en-cn/cpp/cpp/destructors-cpp?view=msvc-170