【C++】带虚基类(virtual)的多继承构造函数执行顺序探究(经验规律)


引言:

  虚基类一般用来解决类多继承中的二义性问题的。比如C所继承的AB都继承了base,那么在AB中对父类base都加一个virtual 关键字,那么就可以避免构造C的时候构造两次base。

  但是!自己用本来用的好好的啥事也没有,但是考试就会出一些代码让你猜猜,不是,让你写出程序的运行结果。

举个例子:(例子有点变态因为是我自己随便写的)

#include 
using namespace std;
class A {
public:
    A()
    {
        cout << "this is A class!\n";
    }
};
class B {
public:
    B()
    {
        cout << "this is B class!\n";
    }
};
class base1
{
public:
    base1()
    {
        cout << "this is base1 class!\n";
    }
};
class base2:virtual public A
{
public:
    base2()
    {
        cout << "this is base2 class!\n";
    }
};
class base3 :public B
{
public:
    base3()
    {
        cout << "this is base3 class!\n";
    }
};
class base4 : virtual public A
{
public:
    base4()
    {
        cout << "this is base4 class!\n";
    }
};
class level1 : public base1, virtual public base2
{
public:
    level1()
    {
        cout << "this is level1 class!\n";
    }
};
class level2 :virtual public base3, public base4
{
public:
    level2()
    {
        cout << "this is level2 class!\n";
    }
};
class toplevel : public level1, virtual public level2
{
public:
    toplevel()
    {
        cout << "this is toplevel class!\n";
    }
};
void main()
{
    toplevel t;
}

来,请你手写出运行结果(摊手)

结果是:(vs2019)

废话不多讲了,直接讲方法了(注意这个规律是我运行大量例子总结而来,并不具有权威指导性,若有不对还请指正)

首先

将整个继承结构看成一个树,把各个类看成节点:

把它倒过来就和平时做的二叉树类似,其中toplevel是整棵树的根节点。

然后我们以如下规则遍历这个树:

对根节点进行操作α

操作α:(注意这整个操作我们将其称为操作α,下面会递归使用)

设当前节点为root
对以root为根节点的树进行第一次遍历(深度优先搜索
若遇到virtual节点,则对之进行操作α
第一次遍历完后,
则对root进行第二次遍历,
遇到未构造过的节点(设为x),则执行x的构造函数。
第二次遍历完后,
已经可以保证root为根的树的所有节点均已执行过构造函数,
此时执行root的构造函数(注意如果root是virtual且与其同名类的构造函数已经执行过不执行)

操作α至此结束。

下面来把这个规则在上面的例子上走一遍:

对toplevel执行操作α

第一次遍历:

  第一个找到的virtual是base2

  对base2进行操作α

    对base2第一次遍历:

       第一个找到的virtual是A

        对A进行操作α

        (因为A是叶子节点,两次遍历直接结束)

        A是virtual且没有构造过,执行A的构造函数    1

        对A操作α结束

    第一次遍历结束

    对base2第二次遍历

    第二次遍历结束

    base2是virtual且没有构造过,执行base2的构造函数   2

  对base2操作α结束

  第二个找到的是level2(注意A不再去遍历了,因为base2的子树都已构造好了)

    对level2进行操作α

      对level2进行第一次遍历

        第一个找到的virtual是base3

          对base3进行操作α

            对base3第一次遍历

            第一次遍历结束

            对base3第二次遍历

              遇到B未被构造,执行B构造函数     3

            第二次遍历结束

            base3是virtual且没有被构造过,执行base3构造函数   4

          对base3操作结束

        第二个找到的virtual是A

          对A进行操作α

            第A两次遍历结束

            因为A是virtual且已经被构造过,所以不执行构造函数(这里解决了二义性问题)

          对A操作结束

      第一次遍历结束

      对level2第二次遍历

        base4未构造过,执行base4构造函数    5

      第二次遍历结束

      level2是virtual且未被构造过,执行level2构造函数    6

    对level2操作结束

toplevel第一次遍历结束

toplevel第二次遍历开始

  执行base1构造函数   7

  执行level1构造函数   8

toplevel第二次遍历结束

因为toplevel未被构造过,执行toplevel构造函数   9

操作结束

以上数字标注部分起来就是最后的执行结果。

总之起来就是两次遍历,第一次遍历先构造virtual,同时保证所构造的virtual节点的所有子节点都完成了构造,第二次遍历构造剩下的部分非virtual节点。可以结合图慢慢体会~

(由于时间原因本文没空慢慢打磨,操作α写的也不是很精简(本来想用代码表示但是发现那样看起来简洁但是更加难解释),还望见谅)

(文中难免会有细节错误,欢迎批评指正)

(最后强调,本文只是根据经验总结出的规律,并不具有权威性和绝对正确性,有懂得大佬欢迎指教)