多重继承,虚继承,MI继承中虚继承中构造函数的调用情况

先来测试一些普通的多重继承。其实这个是显而易见的。

测试代码:

[cpp] view plain copy

print?

  1. //测试多重继承中派生类的构造函数的调用顺序何时调用
  2. //Fedora20 gcc version=4.8.2
  3. #include <iostream>
  4. using namespace std;
  5. class base
  6. {
  7. public:
  8. base()
  9. {
  10. cout<<"base created!"<<endl;
  11. }
  12. ~base()
  13. {
  14. cout<<"base destroyed!"<<endl;
  15. }
  16. };
  17. //公有继承
  18. class A:public base
  19. {
  20. public:
  21. A()
  22. {
  23. cout<<"A created!"<<endl;
  24. }
  25. ~A()
  26. {
  27. cout<<"A destroyed!"<<endl;
  28. }
  29. };
  30. class B:public base
  31. {
  32. public:
  33. B()
  34. {
  35. cout<<"B created!"<<endl;
  36. }
  37. ~B()
  38. {
  39. cout<<"B destroyed!"<<endl;
  40. }
  41. };
  42. class C:public B,public A//多重继承
  43. //改为class C:public A,public B后,AB构造的顺序改变
  44. {
  45. public:
  46. C(){
  47. cout<<"C created!"<<endl;
  48. }
  49. ~C(){
  50. cout<<"C destroyed"<<endl;
  51. }
  52. };
  53. int main()
  54. {
  55. C test;
  56. }

测试结果:

显而易见,普通的多重基层依次调用上一层父类的构造函数。这就说明了MI有时候不太好的情况了,那就是如果你不小心的话,就会从上一层继承关系中意外创建多个基类的对象,例如如果base里面有一个string name属性,那么D创建的时候就会创建两个string name的对象,而这个,不一定是你想要的结果。

现在,来测试一下父类中存在虚继承的情况。

测试代码:

[cpp] view plain copy

print?

  1. //测试多重继承中派生类的构造函数的调用顺序何时调用
  2. //Fedora20 gcc version=4.8.2
  3. #include <iostream>
  4. using namespace std;
  5. class base
  6. {
  7. public:
  8. base(){
  9. cout<<"base created!"<<endl;
  10. }
  11. ~base(){
  12. cout<<"base destroyed!"<<endl;
  13. }
  14. };
  15. //虚继承
  16. class A:public virtual base
  17. {
  18. public:
  19. A(){
  20. cout<<"A created!"<<endl;
  21. }
  22. ~A() {
  23. cout<<"A destroyed!"<<endl;
  24. }
  25. };
  26. //虚继承
  27. class B:public virtual base
  28. {
  29. public:
  30. B(){
  31. cout<<"B created!"<<endl;
  32. }
  33. ~B(){
  34. cout<<"B destroyed!"<<endl;
  35. }
  36. };
  37. //C是虚继承base
  38. class C:public virtual base
  39. {
  40. public:
  41. C(){
  42. cout<<"C created!"<<endl;
  43. }
  44. ~C(){
  45. cout<<"C destroyed"<<endl;
  46. }
  47. };
  48. class D:public A,public B,public C
  49. {//D A,B,C都是虚继承的形式。
  50. public:
  51. D(){
  52. cout<<"D created!"<<endl;
  53. }
  54. ~D(){
  55. cout<<"D destroyed!"<<endl;
  56. }
  57. };
  58. int main()
  59. {
  60. D d;
  61. }

测试结果:

可以看到,如果上一层继承中都是虚继承,那么,只会在最开始一次调用base基类构造函数。

那么,如果A不是虚继承呢?

[cpp] view plain copy

print?

  1. //A不是虚继承
  2. class A:public base
  3. {
  4. public:
  5. A(){
  6. cout<<"A created!"<<endl;
  7. }
  8. ~A() {
  9. cout<<"A destroyed!"<<endl;
  10. }
  11. };

测试结果:

看到虚继承的B,C依旧只是在最开始使调用了一次base,但是A类不再是虚继承,因此A类的构造函数也调用来一次base的构造函数.

[[email protected] csuper]$ ./c
base created!      //这是BC共同调用的base构造函数
base created!     //这是调用A类的构造函数时,A类构造函数又调用了一次base的构造函数。
A created!

为了测试这一想法是否真是如此,这里我们利用控制变量法,仅使B类不是虚继承。

[cpp] view plain copy

print?

  1. //仅令B不是虚继承,A,C依旧是虚继承
  2. class B:public  base
  3. {
  4. public:
  5. B(){
  6. cout<<"B created!"<<endl;
  7. }
  8. ~B(){
  9. cout<<"B destroyed!"<<endl;
  10. }
  11. };

结果:

可以看出,结果正是如此。

同时发现了一个有趣的情况。当A,B,C都是虚继承base的时候,D虚继承C,看看结果又会如何?

[cpp] view plain copy

print?

  1. class D:public A,public B,virtual public C
  2. {// A,B,C都是虚继承base的形式。

[cpp] view plain copy

print?

  1. //D又虚继承C
  2. public:
  3. D(){
  4. cout<<"D created!"<<endl;
  5. }
  6. ~D(){
  7. cout<<"D destroyed!"<<endl;
  8. }
  9. };

结果:

可以看到,构造D的对象时,先调用了base,然后就到了调用C的构造函数了,说明编译器在构造的时候,是优先构造虚继承的对象的,这样就保证了构造A,B对象的时候,如果AB是虚继承于base,就不会创建多个从base定义的成员属性了。

但是如果C不是虚继承base,但D又是虚继承C的时候又会如何呢?

[cpp] view plain copy

print?

  1. //C不是虚继承base
  2. class C:public base
  3. {
  4. public:
  5. C(){
  6. cout<<"C created!"<<endl;
  7. }
  8. ~C(){
  9. cout<<"C destroyed"<<endl;
  10. }
  11. };
  12. class D:public A,public B,virtual public C
  13. {//D A,B,C都是虚继承的形式。
  14. public:
  15. D(){
  16. cout<<"D created!"<<endl;
  17. }
  18. ~D(){
  19. cout<<"D destroyed!"<<endl;
  20. }
  21. };

结果:

可以看出,第一个调用的base应该是属于A,B调用的,因此,其实上面的说法是不对的,因为如果是优先调用C的构造函数,输出应该是

base created!   //如果是优先调用C
C created!

base created!   //A,B调用的base应该在这里出现,但事实上却不是。
A created!
B created!
D created!
...........

再看看下面的测试:

[cpp] view plain copy

print?

  1. //测试多重继承中派生类的构造函数的调用顺序何时调用
  2. //Fedora20 gcc version=4.8.2
  3. #include <iostream>
  4. using namespace std;
  5. class base
  6. {
  7. public:
  8. base(){
  9. cout<<"base created!"<<endl;
  10. }
  11. ~base(){
  12. cout<<"base destroyed!"<<endl;
  13. }
  14. };
  15. //A是虚继承
  16. class A:public virtual base
  17. {
  18. public:
  19. A(){
  20. cout<<"A created!"<<endl;
  21. }
  22. ~A() {
  23. cout<<"A destroyed!"<<endl;
  24. }
  25. };
  26. //B不是虚继承
  27. class B:public  base
  28. {
  29. public:
  30. B(){
  31. cout<<"B created!"<<endl;
  32. }
  33. ~B(){
  34. cout<<"B destroyed!"<<endl;
  35. }
  36. };
  37. //C不是虚继承base
  38. class C:public base
  39. {
  40. public:
  41. C(){
  42. cout<<"C created!"<<endl;
  43. }
  44. ~C(){
  45. cout<<"C destroyed"<<endl;
  46. }
  47. };
  48. class D:public A,public virtual B,virtual public C
  49. {// B,C都是虚继承的形式。
  50. public:
  51. D(){
  52. cout<<"D created!"<<endl;
  53. }
  54. ~D(){
  55. cout<<"D destroyed!"<<endl;
  56. }
  57. };
  58. int main()
  59. {
  60. D d;
  61. }

这里只有A是虚继承base,B,C都不是虚继承,但是在D里面,刚好相反,看看结果如何

说实在的,输出是这样我是没有想到的。

base created!   //这个base是哪一个调用的呢?
base created!
B created!
base created!
C created!

后来我再这样测试一下我就知道是为什么了。

令A,B,C均不虚继承与base,但是D继承与A,并且虚继承B,C,看结果

因此可以看出,上一次的第一个base是因为A而调用的base,也就是说,编译器是先检测上一层继承关系(A,B,C)中,哪一个是虚继承于再上一层的类(base),如果有,则优先调用该虚继承与base的类(也就是A)的基类的(也就是base)构造函数,然后再调用D中虚继承的类的构造函数(B,C),最后才调用A自己的构造函数,这里有点复杂,还是上一次测试的例子:

base created!   //这个base是因为A是虚继承与base时调用的,但是调用完之后并不直接调用A (A created!)
base created!   //这个是调用B时调用的base
B created!
base created!   //这个是调用C时i调用的base
C created!

A created!    //最后才调用A created!

因此,可以发现,如果你不想该类(例如A)在被继承的时候防止派生类(D)多次创建该类(A)的基类(base)的多个对象,那么,就应该将该类声明为virtual 继承基类(base),vitual的作用是对下一层次的派生类起作用,对该类(A)并不起特别作用(但是该类(D)会优先调用虚继承类(B,C)的构造函数)。

时间: 2024-10-10 08:33:15

多重继承,虚继承,MI继承中虚继承中构造函数的调用情况的相关文章

Unity中Awake与Start函数的调用情况总结(转)

在Unity中编写脚本时,有一系列的可重写(override)函数供我们使用,其中的Awake与Start两个函数作为初始化与设置之用,几乎在每个脚本中都要用到.因此,正确的把握这两个函数的调用时机,就能让我们在程序开发过程中避免一些错误,提高开发效率.比较懒,所以就没有上图,欢迎大家的批评指正: ) 1.  Awake函数 首先,我们来看unity的参考手册中对Awake函数的一些说明情况: 当脚本实例被加载时会调用Awake函数:Awake函数在所有的游戏对象被初始化完毕之后才会被调用:在脚

对象序列化中 子类和父类构造函数的调用问题

第三种情况:

java中static程序块和构造函数的执行情况

参考博文:http://blog.csdn.net/gxf212/article/details/3510009 class Base { static int a = 1; public Base() { System.out.println(" 55555"); } static { System.out.println("Static Base"); } static void method() { System.out.println("Base&

c++学习笔记5,多重继承中派生类的构造函数与析构函数的调用顺序(二)

现在来测试一下在多重继承,虚继承,MI继承中虚继承中构造函数的调用情况. 先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; }

C++中虚函数工作原理和(虚)继承类…

转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理 虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数.典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式.vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl.当

C++中虚函数工作原理和(虚)继承类的内存占用大小计算

一.虚继承情况下类的内存大小计算 当每个基类中有多个虚函数时,并且在虚继承的情况下,内存是如何分配的,如何计算类的大小,下面举例说明: #include<iostream> using namespace std; class A { public: int a; virtual void aa(){}; }; class D { public: virtual void dd(){}; }; class C { public: virtual void cc(){}; }; class B

C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

1.虚函数(impure virtual) C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现. 子类可以重写父类的虚函数实现子类的特殊化. 如下就是一个父类中的虚函数: class A { public: virtual void out2(string s) { cout<<"A(out2):"<<s<<endl; } }; 2.纯虚函数(pure virtual) C++中包含纯虚函数的类,被称为是“抽象类

C++中虚继承类构造函数的正确写法

最近工作中某个软件功能出现了退化,追查下来发现是一个类的成员变量没有被正确的初始化.这个问题与C++存在虚继承的情况下派生类构造函数的写法有关.在此说明一下错误发生的原因,希望对更多的人有帮助. 我们代码中存在虚继承的类的继承结构与下图类似,并不是教科书中经典的菱形结构.从 Intermediate1 和 Intermediate3 到Base2 的继承都是虚继承.Base1 和 Base2 都包含一些成员变量,并提供了相应的构造函数接受指定的初始化值.Base2 还有一个缺省构造函数,把其成员

&lt;转&gt;C++继承中虚函数的使用

转自:http://blog.csdn.net/itolfn/article/details/7412364 一:继承中的指针问题. 1. 指向基类的指针可以指向派生类对象,当基类指针指向派生类对象时,这种指针只能访问派生对象从基类继承 而来的那些成员,不能访问子类特有的元素 ,除非应用强类型转换,例如有基类B和从B派生的子类D,则B *p;D  dd; p=&dd;是可以的,指针p只能访问从基类派生而来的成员,不能访问派生类D特有的成员.因为基类不 知道派生类中的这些成员. 2. 不能使派生类