C++虚继承解说

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的三个成员函数。

时间: 2024-10-11 11:54:12

C++虚继承解说的相关文章

C++ 虚函数和虚继承浅析

本文针对C++里的虚函数,虚继承表现和原理进行一些简单分析,有希望对大家学习C++有所帮助.下面都是以VC2008编译器对这两种机制内部实现为例. 虚函数 以下是百度百科对于虚函数的解释: 定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数[1] 语法:virtual 函数返回类型 函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 函数声明和定义和普通的类成员函数一样,只是在返回值之前加入了关键字"vir

多继承(虚继承)派生类对象内存结构

在这里谈一下虚继承.前面写过派生类对象的内存结构,都是基于VS2010编译器的,不同的编译器对于继承的处理不同,但本质都是一样的. 虚继承是解决共享基类问题的.例如在菱形继承中 如果不使用虚继承,基类A在D中会有两个,这不仅浪费内存,还会造成歧义.使用虚继承就可以解决基类共享的问题. 要想在派生类中共享基类(例如在D对象中只有一个A对象,这时候D对象中的B对象和C对象都可以查找到A,而不是在B对象和C对象中各含有一个A对象). 先看下面一个例子: #include<iostream> usin

sizeof 和类继承 虚继承 求类大小

代码: #include <iostream> using namespace std; /* class a{ float k; // 4字节 virtual void foo(){} //有一个4字节的指针指向自己的虚函数表 }; class b : virtual public a{ virtual void f(){} }; 有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节:还要包括类a的字节数,所以类b的字节数就求出来了. 运行结果: 8 16 */ /* clas

C++多重继承中的虚继承和虚函数举例

上一篇虚继承举例:http://10638473.blog.51cto.com/10628473/1964414 本文将A类中的show()函数前加上virtual关键字. //多重继承 #include <iostream> using namespace std; class A { public:     int a;     A(int a=0):a(a)     {         cout<<"A基类A::A()"<<endl;     

虚继承

------------------siwuxie095 看如下实例: 有 4 个类,其中:类 A 是父类,类 B 和 类 C 都继承 类 A, 而 类 D 继承了 类 B 和 类 C,称这种继承关系为 菱形继承 在菱形继承中,既有多继承,又有多重继承: 那么问题来了: 当实例化 D 的对象时,发现:D 是从 B 继承来的,B 是从 A 继承来的, D 也是从 C 继承来的,C 是从 A 继承来的 这样,D 中将含有两个完全一样的 A 的数据,这种情况是不能容忍的, 因为在一个对象中有两份完全相

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

先来测试一些普通的多重继承.其实这个是显而易见的. 测试代码: [cpp] view plain copy print? //测试多重继承中派生类的构造函数的调用顺序何时调用 //Fedora20 gcc version=4.8.2 #include <iostream> using namespace std; class base { public: base() { cout<<"base created!"<<endl; } ~base()

看到的关于虚函数继承及虚继承解释比较好的文章的复制

(来源于:http://blog.chinaunix.net/uid-25132162-id-1564955.html) 1.空类,空类单继承,空类多继承的sizeof #include <iostream> using namespace std; class Base1 { }; class Base2 { }; class Derived1:public Base1 { }; class Derived2:public Base1, public Base2 { }; int main(

C++构造函数 &amp; 拷贝构造函数 &amp; 派生类的构造函数 &amp; 虚继承的构造函数

构造函数 ,是一种特殊的方法 .主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 .特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载.(摘自百度百科构造函数). 一.最基本的构造函数 1 class Base 2 { 3 public: 4 Base(int var) : m_Var(var) 5 { 6 } 7 private: 8 int m_Var; 9 }; 以上构造函数的执行过程:

C++中的虚继承 &amp; 重载隐藏覆盖的讨论

虚继承这个东西用的真不多.估计也就是面试的时候会用到吧.. 可以看这篇文章:<关于C++中的虚拟继承的一些总结> 虚拟基类是为解决多重继承而出现的. 如:类D继承自类B1.B2,而类B1.B2都继承自类A,因此在类D中两次出现类A中的变量和函数.为了节省内存空间,可以将B1.B2对A的继承定义为虚拟继承,而A就成了虚拟基类.实现的代码如下: class A class B1:public virtual A; class B2:public virtual A; class D:public