• 认真地记录技术中遇到的坑!

菱形继承问题和虚继承【转载】

程序开发语言 Moxun 7个月前 (05-29) 269次浏览 0个评论

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

1.菱形继承
菱形继承的样子:
菱形继承问题和虚继承【转载】

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

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就蛋疼报错了。谁知道你要用哪一个?同样的访问成员函数也会出现“二义性”这个问题。

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

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

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();
}

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

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

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

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。

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。为了解决这个重复初始化的问题,虚继承对初始化进行了特殊处理。在虚继承中,由最底层派生类的构造函数初始化虚基类。体会一下下面这个例子:
菱形继承问题和虚继承【转载】

输出构造和析构顺序:

C()
E()
A()
B()
D()
F()
~F()
~D()
~B()
~A()
~E()
~C()

可以看出,首先按声明顺序检查直接基类(包括其子树),看是否有虚基类,先初始化虚基类(例中首先初始化C和E)。一旦虚基类构造完毕,就按声明顺序调用非虚基类的构造函数(例中ABDF),析构的调用次序和构造调用次序相反。


喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址