菱形继承解决方案--虚基类

转载自:黑马程序员(因无链接可寻)

 

两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。

这种继承所带来的问题:

  1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
  2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

class BigBase{

public:

BigBase(){ mParam = 0; }

void func(){ cout << "BigBase::func" << endl; }

public:

int mParam;

};

 

class Base1 : public BigBase{};

class Base2 : public BigBase{};

class Derived : public Base1, public Base2{};

 

int main(){

 

Derived derived;

//1. 对“func”的访问不明确

//derived.func();

//cout << derived.mParam << endl;

cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;

cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;

 

//2. 重复继承

cout << "Derived size:" << sizeof(Derived) << endl; //8

 

return EXIT_SUCCESS;

}

 

上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决,那么重复继承怎么解决?

对于这种菱形继承所带来的两个问题,c++为我们提供了一种方式,采用虚基类。那么我们采用虚基类方式将代码修改如下:

class BigBase{

public:

BigBase(){ mParam = 0; }

void func(){ cout << "BigBase::func" << endl; }

public:

int mParam;

};

 

class Base1 : virtual public BigBase{};

class Base2 : virtual public BigBase{};

class Derived : public Base1, public Base2{};

 

int main(){

 

Derived derived;

//二义性问题解决

derived.func();

cout << derived.mParam << endl;

//输出结果:12

cout << "Derived size:" << sizeof(Derived) << endl;

 

return EXIT_SUCCESS;

}

 

以上程序Base1 ,Base2采用虚继承方式继承BigBase,那么BigBase被称为虚基类。

通过虚继承解决了菱形继承所带来的二义性问题。

但是虚基类是如何解决二义性的呢?并且derived大小为12字节,这是怎么回事?

4.7.6.3 虚继承实现原理

class BigBase{

public:

BigBase(){ mParam = 0; }

void func(){ cout << "BigBase::func" << endl; }

public: int mParam;

};

#if 0 //虚继承

class Base1 : virtual public BigBase{};

class Base2 : virtual public BigBase{};

#else //普通继承

class Base1 :  public BigBase{};

class Base2 :  public BigBase{};

#endif

class Derived : public Base1, public Base2{};

 

通过对象布局图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。

 

  • BigBase 菱形最顶层的类,内存布局图没有发生改变。
  • Base1和Base2通过虚继承的方式派生自BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。
  • Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。

 

由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承一份数据,并且也解决了二义性的问题。现在模型就变成了Base1和 Base2 、Derived三个类对象共享了一份BigBase数据。

 

当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用

 

class BigBase{

public:

BigBase(int x){mParam = x;}

void func(){cout << "BigBase::func" << endl;}

public:

int mParam;

};

class Base1 : virtual public BigBase{

public:

Base1() :BigBase(10){} //不调用BigBase构造

};

class Base2 : virtual public BigBase{

public:

Base2() :BigBase(10){} //不调用BigBase构造

};

 

class Derived : public Base1, public Base2{

public:

Derived() :BigBase(10){} //调用BigBase构造

};

//每一次继承子类中都必须书写初始化语句

int main(){

Derived derived;

return EXIT_SUCCESS;

}

 

注意:

    虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的.

 

工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。

 

   Jerry Schwarz,输入输出流(iostream)的作者,曾在个别场合表示如何他重新设计iostream的话,很可能从iostream中去除多重继承。

 

这种继承所带来的问题:

  1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
  2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

class BigBase{

public:

BigBase(){ mParam = 0; }

void func(){ cout << "BigBase::func" << endl; }

public:

int mParam;

};

 

class Base1 : public BigBase{};

class Base2 : public BigBase{};

class Derived : public Base1, public Base2{};

 

int main(){

 

Derived derived;

//1. 对“func”的访问不明确

//derived.func();

//cout << derived.mParam << endl;

cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;

cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;

 

//2. 重复继承

cout << "Derived size:" << sizeof(Derived) << endl; //8

 

return EXIT_SUCCESS;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值