【C++】带虚基类(virtual)的多继承构造函数执行顺序探究(经验规律)
引言:
虚基类一般用来解决类多继承中的二义性问题的。比如C所继承的A和B都继承了base,那么在A和B中对父类base都加一个virtual 关键字,那么就可以避免构造C的时候构造两次base。
但是!自己用本来用的好好的啥事也没有,但是考试就会出一些代码让你猜猜,不是,让你写出程序的运行结果。
举个例子:(例子有点变态因为是我自己随便写的)
#includeusing 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节点。可以结合图慢慢体会~
(由于时间原因本文没空慢慢打磨,操作α写的也不是很精简(本来想用代码表示但是发现那样看起来简洁但是更加难解释),还望见谅)
(文中难免会有细节错误,欢迎批评指正)
(最后强调,本文只是根据经验总结出的规律,并不具有权威性和绝对正确性,有懂得大佬欢迎指教)