【C++基础之二十一】菱形继承和虚继承

【C++基础之二十一】菱形继承和虚继承

标签: c++菱形继承虚继承虚基类
8645人阅读 评论(2) 收藏 举报
分类:

原创作品,转载请标明:http://blog.csdn.net/jackystudio/article/details/17877219


菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口。C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口。在多重继承中建议使用“接口”,来避免多重继承中可能出现的各种问题。


1.菱形继承

先看一下菱形继承长什么样。



B和C从A中继承,而D多重继承于B,C。那就意味着D中会有A中的两个拷贝。因为成员函数不体现在类的内存大小上,所以实际上可以看到的情况是D的内存分布中含有2组A的成员变量。如下代码:

  1. class A  
  2. {  
  3. public:  
  4.     A():a(1){};  
  5.     void printA(){cout<<a<<endl;}  
  6.     int a;  
  7. };  
  8.   
  9. class B : public A  
  10. {  
  11. };  
  12.   
  13. class C : public A  
  14. {  
  15. };  
  16.   
  17. class D:  public B ,  public C  
  18. {  
  19. };  
  20.   
  21. int _tmain(int argc, _TCHAR* argv[])  
  22. {  
  23.     D d;  
  24.     cout<<sizeof(d);  
  25. }  
class A
{
public:
	A():a(1){};
	void printA(){cout<<a<<endl;}
	int a;
};

class B : public A
{
};

class C : public A
{
};

class D:  public B ,  public C
{
};

int _tmain(int argc, _TCHAR* argv[])
{
	D d;
	cout<<sizeof(d);
}

输出d的大小为8。也就是d中有2个a成员变量。这样一来如果要使用a就蛋疼报错了。谁知道你要用哪一个?同样的访问成员函数也会出现“二义性”这个问题。

  1. d.a=10;//error C2385: 对“a”的访问不明确  
  2. d.printA();//error C2385: 对“printA”的访问不明确  
	d.a=10;//error C2385: 对“a”的访问不明确
	d.printA();//error C2385: 对“printA”的访问不明确

即使成员函数有不同的形参表也不行,如下。

  1. class A  
  2. {  
  3. public:  
  4.     A():a(1){};  
  5.     void printA(){cout<<a<<endl;}  
  6.     int a;  
  7. };  
  8.   
  9. class B : public A  
  10. {  
  11. public:  
  12.     void printB(){cout<<"from B"<<endl;}  
  13. };  
  14.   
  15. class C : public A  
  16. {  
  17. public:  
  18.     void printB(int v){cout<<"from C"<<endl;}  
  19. };  
  20.   
  21. class D:  public B ,  public C  
  22. {  
  23. };  
  24.   
  25. int _tmain(int argc, _TCHAR* argv[])  
  26. {  
  27.     D d;  
  28.     cout<<sizeof(d);  
  29.     d.printB();  
  30. }  
class A
{
public:
	A():a(1){};
	void printA(){cout<<a<<endl;}
	int a;
};

class B : public A
{
public:
	void printB(){cout<<"from B"<<endl;}
};

class C : public A
{
public:
	void printB(int v){cout<<"from C"<<endl;}
};

class D:  public B ,  public C
{
};

int _tmain(int argc, _TCHAR* argv[])
{
	D d;
	cout<<sizeof(d);
	d.printB();
}

当然你可以无耻地使用作用域来规避这个问题。但这并不是一个好主意。

  1. d.B::a=10;  
  2. d.C::printB(1);  
	d.B::a=10;
	d.C::printB(1);


2.虚继承


2.1.概念

这时候虚继承就挺身而出,扛下搞定此问题的重担了。虚继承是一种机制,类通过虚继承指出它希望共享虚基类的状态。对给定的虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象,共享基类子对象称为虚基类。虚基类用virtual声明继承关系就行了。这样一来,D就只有A的一份拷贝。如下:

  1. class A  
  2. {  
  3. public:  
  4.     A():a(1){};  
  5.     void printA(){cout<<a<<endl;}  
  6.     int a;  
  7. };  
  8.   
  9. class B : virtual public A  
  10. {  
  11. };  
  12.   
  13. class C : virtual public A  
  14. {  
  15. };  
  16.   
  17. class D:  public B ,  public C  
  18. {  
  19. };  
  20.   
  21. int _tmain(int argc, _TCHAR* argv[])  
  22. {  
  23.     D d;  
  24.     cout<<sizeof(d);  
  25.     d.a=10;  
  26.     d.printA();  
  27. }  
class A
{
public:
	A():a(1){};
	void printA(){cout<<a<<endl;}
	int a;
};

class B : virtual public A
{
};

class C : virtual public A
{
};

class D:  public B ,  public C
{
};

int _tmain(int argc, _TCHAR* argv[])
{
	D d;
	cout<<sizeof(d);
	d.a=10;
	d.printA();
}

输出d的大小是12(包含了2个4字节的D类虚基指针和1个4字节的int型整数)。而a和printA()都可以正常访问。最典型的应用就是iostream继承于istream和ostream,而istream和ostream虚继承于ios。

  1. class istream : virtual public ios{...};  
  2. class ostream : virtual public ios{...};  
  3. class iostream : public istream, public ostream{...};  
class istream : virtual public ios{...};
class ostream : virtual public ios{...};
class iostream : public istream, public ostream{...};

2.2.注意

(1)支持到基类的常规转换。也就是说即使基类是虚基类,也照样可以通过基类指针或引用来操纵派生类的对象。

(2)虚继承只是解决了菱形继承中派生类多个基类内存拷贝的问题,并没有解决多重继承的二义性问题。

(3)通常每个类只会初始化自己的直接基类,如果不按虚继承处理,那么在菱形继承中会出现基类被初始两次的情况,在上例中也就是A→B→A→C→D。为了解决这个重复初始化的问题,虚继承对初始化进行了特殊处理。在虚继承中,由最底层派生类的构造函数初始化虚基类。体会一下下面这个例子:


输出构造和析构顺序:

  1. C()  
  2. E()  
  3. A()  
  4. B()  
  5. D()  
  6. F()  
  7. ~F()  
  8. ~D()  
  9. ~B()  
  10. ~A()  
  11. ~E()  
  12. ~C()  
C()
E()
A()
B()
D()
F()
~F()
~D()
~B()
~A()
~E()
~C()

可以看出,首先按声明顺序检查直接基类(包括其子树),看是否有虚基类,先初始化虚基类(例中首先初始化C和E)。一旦虚基类构造完毕,就按声明顺序调用非虚基类的构造函数(例中ABDF),析构的调用次序和构造调用次序相反。
4
0
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值