C++虚继承解说
更新:HHH   时间:2023-1-7


1.概括

    在CPlusPlus多继承编程中时常遇到这样一个问题--若子类实现多个基类或接口继承,多基类或接口中存在成员名相同,在客户与实现类之间的通信时编译器报错“不能这样使用,会产生二义性”由于这个问题的解决方法很多。比如说,可以把相同的成员名给改过来。但是,从专业的角度,可能虚拟继承会解决这个问题。那接下来我看看c++是怎么避免这种问题的。


2.概念

    当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为虚基类。       class 派生类名:virtual 继承方式  基类名    

    virtual是关键字,声明该基类为派生类的虚基类。    

    例如:     

    class 派生类: virtual 基类1,virtual 基类2,...,virtual 基类n
    
    {
        
        ...//派生类成员声明
    
    };

    在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。 声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。C++使用虚拟继承(Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。这样带来的有点是解决了二义性问题,也节省了内存,避免了数据不一致的问题。


3.用例

二义性:

#include <iostream>
using namespace std;
 //Base 
class Base
{
 public:
 Base(){cout << "Base called..."<< endl;}
 void print(){cout << "Base print..." <<endl;}
  private:
 };
 
 //Sub 
class Sub //定义一个类 Sub 
{
public:
 Sub(){cout << "Sub called..." << endl;}
  void print(){cout << "Sub print..." << endl;}
private:
};

  //Child 
class Child : public Base , public Sub //定义一个类Child 分别继承自 Base ,Sub 
{
  public:
  Child(){cout << "Child called..." << endl;}
 private:
 };
 
int main(int argc, char* argv[])
{
 Child c;
  //不能这样使用,会产生二意性,VC下error C2385 
  //c.print();  
 
 //只能这样使用 
  c.Base::print();
  c.Sub::print();
  system("pause");
  return 0;
  }


多重继承:

//说明:C++虚拟继承学习演示  
//环境:VS2005  
//blog:pppboy.blog.163.com  
//----------------------------------------------------  
#include "stdafx.h"  
#include <iostream>
using namespace std;  
int gFlag = 0;  
class Base  
{
public:  
 Base(){cout << "Base called : " << gFlag++ << endl;}  
 void print(){cout << "Base print" <<endl;}  
};

class Mid1 : public Base  
{
 public:  
 Mid1(){cout << "Mid1 called" << endl;}  
 private:  
};

class Mid2 : public Base  
{
public:  
 Mid2(){cout << "Mid2 called" << endl;}  
};

class Child:public Mid1, public Mid2  
{
public:  
 Child(){cout << "Child called" << endl;}  
};

int main(int argc, char* argv[])  
{
Child d;
//不能这样使用,会产生二意性  
     //d.print();  
//只能这样使用  
d.Mid1::print();
d.Mid2::print();
system("pause");  
return 0;  
}

output:

Base called : 0
 Mid1 called
 Base called : 1
 Mid2 called
 Child called
 Base print
 Base print

虚拟继承:

#include "stdafx.h"  
#include <iostream>
using namespace std;  
int gFlag = 0;  
class Base  
{
public:  
Base(){cout << "Base called : " << gFlag++ << endl;}  
void print(){cout << "Base print" <<endl;}  
};

class Mid1 : virtual public Base  
{
public:  
Mid1(){cout << "Mid1 called" << endl;}  
private:  
};

class Mid2 : virtual public Base  
{
public:  
Mid2(){cout << "Mid2 called" << endl;}  
};

class Child:public Mid1, public Mid2  
{
public:  
Child(){cout << "Child called" << endl;}  
};

int main(int argc, char* argv[])  
{
Child d;
//这里可以这样使用  
d.print();
//也可以这样使用  
d.Mid1::print();
d.Mid2::print();
system("pause");  
return 0;  
}


4.总结

    在多继承情况下,虚基类关键字的作用范围和继承方式关键字相同,只对紧跟其后的基类起作用。声明了虚基类之后,虚基类在进一步派生过程中始终和派生类一起,维护同一个基类子对象的拷贝。观察类构造函数的构造顺序,拷贝也只有一份。


5.扩展

    windows编程的程序员们在进行COM编程的时候会遇到这样的一个问题------继承接口IUnknown这一块使用非虚拟继承。这是为什么呢?如果有这样的疑问是很正常。之所以这样是由于会导致与COM不兼容的vtbl。比如:

    struct IX : public IUnknown

    {

        //....

    };

    struct IY : public IUnknow

    {

        //....

    };


 客户程序实现:

...
if(iid == IID_IUnknown) {
        //the client wants the IUnknown interface.
        *ppv = static_cast<IX*>(this);
    } else if(iid == IID_IX){
        //the client wants the IX interface.
     *ppv = static_cast<IX*>(this);
    } else if(iid == IID_IY) {
        *ppv = static_cast<IY*>(this);
    }
    ...

    可见,程序中他们是通过类型转换的。不然,IX和IY的vtbl中的头三个函数指向的将不是IUnknown的三个成员函数。

    


    

返回编程语言教程...