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

引言:

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

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

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

#include <iostream>
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节点。可以结合图慢慢体会~

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

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

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

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在多重继承中,如果有虚基类,派生类的构造函数必须负责调用虚基类构造函数虚基类构造函数的调用顺序是按照它们在继承层次中出现的顺序来决定的,而不是按照它们在成员初始化列表中的顺序来决定的。 具体来说,派生类在构造函数中必须显式调用虚基类构造函数,而且必须在成员初始化列表中完成。如果没有显式调用虚基类构造函数,编译器会调用默认的构造函数,这可能导致程序出错。 示例代码如下: ```c++ class Animal { public: Animal(int age) { cout << "Animal constructor, age = " << age << endl; } }; class Mammal : virtual public Animal { public: Mammal(int age) : Animal(age) { cout << "Mammal constructor" << endl; } }; class WingedAnimal : virtual public Animal { public: WingedAnimal(int age) : Animal(age) { cout << "WingedAnimal constructor" << endl; } }; class Bat : public Mammal, public WingedAnimal { public: Bat(int age) : Animal(age), Mammal(age), WingedAnimal(age) { cout << "Bat constructor" << endl; } }; int main() { Bat bat(5); return 0; } ``` 输出结果为: ``` Animal constructor, age = 5 WingedAnimal constructor Mammal constructor Bat constructor ``` 在上面的例子中,Animal是虚基类,Mammal和WingedAnimal都通过虚继承来继承Animal。Bat类继承了Mammal和WingedAnimal,它必须显式调用Animal、Mammal和WingedAnimal的构造函数,并且必须按照它们在继承层次中出现的顺序来调用。因此,Bat的构造函数中先调用Animal的构造函数,然后调用WingedAnimal的构造函数,最后调用Mammal的构造函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡咖_CoffCa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值