[收集]虚继承

一、什么是虚继承?

虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系之间的巨大的差异(包括访问性能上的差异)。

首先还是先给出虚继承和虚基类的定义。

虚继承:在继承定义中包含了virtual关键字的继承关系;

虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:

class CSubClass : public virtual CBase {};

其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不是虚继承体系中的基类。

虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句,但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。

 

二、虚继承的内存模型

(1)   存在

也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择。

(2)   间接

间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型的值)

(3)   共享

共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。


三、虚继承情况下的类的内存布局

(1)   如何查看内存布局

VS2008使用命令行选项查看对象的内存布局

微软的Visual Studio 2008(VS2008)提供了一个新的选项,给用户显示C++对象在内存中的布局。

这个选项就是/d1reportSingleClassLayout。

具体使用方法如下,在写好相应的cpp文件之后,需要启动VS2008的命令行工具“Visual Studio 2008 Command Prompt”(Visual Studio 2008 ->Visual Studio Tools->Visual Studio 2008 Command Prompt),切换到cpp文件所在目录之后,输入如下的命令:

cl [filename].cpp/d1reportSingleClassLayout[className]

cl当然就是MS的编译器咯;[filename].cpp就是你所想要查看的class所在的cpp文件(class定义在头文件也没关系,还是只要编译cpp文件即可);而你需要在最后加上[className],也就是你需要查看的class的类名。

【举例】

Test.cpp文件代码如下:

#include<iostream>
using namespacestd;
 
class Base
{
public:
    int a;
    virtual void fcn() {};
};
 
class Derived :public Base
{
public:
    virtual void fcn2() {};
private:
    int d;
    void fcn3() { }
};
 
int main(void)
{
}

我想查看Derived这个类的对象在内存中的布局,那么就可以用下面的命令行。

cl Test.cpp/d1reportSingleClassLayoutDerived



可以看到class Derived的对象的内存布局,在派生类对象的开始包含了基类Base的对象,其中有一个虚表指针,指向的就是下面的Derived::$vftable@ (virtual function table),表中包含了Derived类中所有的虚函数。

 

 

【再举一例】

下面是一个经典的虚继承的菱形继承结构的代码:

#include<iostream>
using namespacestd;
 
class Base
{
public:
    int a;
    virtual void fcn1() {};
};
 
class Derived1 :public Base
{
public:
    int b;
    virtual void fcn2() {};
};
 
class Derived2 :public Base
{
public:
    int c;
    virtual void fcn3() {};
};
 
class Child :virtual public Derived1, virtual public Derived2
{
public:
    int d;
    virtual void fcn4() {} ;
};
 
int main(void)
{
}

其对应的显示结果则如下:


其中{vbptr}表示虚继承的虚基类指针。然后这个Child类其实有三个虚函数表指针(图中的三个{vfptr}),下面则分别给出了虚基类指针和三个虚函数表指针的具体内容。其中的那些负数表示这些指针举例对象起始位置的offset。

 

 

(2)   虚继承情况下的类的内存布局


Code
#include <iostream>
using namespace std;
 
class B
{
public:
    int i;
    virtual void vB(){ cout << "B::vB" << endl; }
    void fB(){ cout << "B::fB" << endl;}
};
 
class D1 : virtual public B
{
public:
    int x;
    virtual void vD1(){ cout << "D1::vD1" << endl; }
    void fD1(){ cout << "D1::fD1" << endl;}
};
 
class D2 : virtual public B
{
public:
    int y;
    void vB(){ cout << "D2::vB" << endl;}
    virtual void vD2(){ cout << "D2::vD2" << endl;}
    void fD2(){ cout << "D2::fD2" << endl;}
};
 
class GD :  public D1, public D2
{
public:
    int a;
    void vB(){ cout << "GD::vB" << endl;}
    void vD1(){cout << "GD::vD1" << endl;}
    virtual void vGD(){cout << "GD::vGD" << endl;}
    void fGD(){cout << "GD::fGD" << endl;}
};


类图: 



VS2008的编译选项查看布局:

 

可视化表示:


 

四、 虚继承情况下的类的大小

 

(1)对于虚继承的子类,其sizeof的值是其父类成员,加上它自己的成员,以及它自己一个指向父类的指针(大小为4)(指向虚基类的指针,区别虚函数表指针),对齐后的sizeof。如:

 

#include   <iostream.h>  
   
  class   a  
  {  
  private:  
  int   x;  
  };  
   
   
   
  class   b:   virtual   public   a  
  {  
  private:  
  int   y;  
  };  
   
  class   c:   virtual   public   a  
  {  
  private:  
  int   z;  
  };  
   
  class   d:public   b,public   c  
  {  
  private:  
  int   m;  
  };  
  int   main(int   argc,   char*   argv[])  
  {  
  cout<<sizeof(a)<<endl;  
  cout<<sizeof(b)<<endl;  
  cout<<sizeof(c)<<endl;  
  cout<<sizeof(d)<<endl;  
  return   0;  
  } 


在VC6.0下调试结果为   

  4  

  12  

  12  

  24

 

sizeof(b)和sizeof(c)相同,都是4+4+4=12。

 

sizeof(d)是sizeof(b)(为12)+sizeof(c)(为12)-b和c相同的部分(a的成员,大小是4)+d自己的成员(大小为4)=24

 

(2)对于既有虚继承又有虚函数的子类,其sizeof的值是其父类成员(计算虚表指针大小+4),加上它自己的成员(计算虚表指针大小+4),以及它自己一个指向父类的指针(大小为4),对齐后的sizeof

 

class Base
{
public:
 Base(){cout<<"Base-ctor"<<endl;}
 ~Base(){cout<<"Base-dtor"<<endl;}
 virtual void f(int) {cout<<"Base::f(int)"<<endl;}
virtual void f(double){cout<<"Base::f(double)"<<endl;}
};
 
class Derived:virtual public Base
{
public:
 Derived(){cout<<"Derived-ctor"<<endl;}
 virtual void g(int){cout<<"Derived::g(int)"<<endl;}
};

 

sizeof(Base)=4

 

sizeof(Derived)=12 (父类虚表指针大小4+自己虚表指针大小4+子类指向父类的一个指针大小4=12)

(如果是class Derived: public Base, 则sizeof(Derived)=4)

 

五、  虚基表(vbtable)与虚函数表指针(vbptr)、虚函数表(vftable)与虚函数表指针(vfptr)

 

 

 

 

 

参考:

http://www.cppblog.com/chemz/archive/2007/06/12/26135.html

http://www.cnblogs.com/microgrape/archive/2011/05/11/2043794.html

http://www.cnblogs.com/itech/archive/2009/03/01/1399996.html

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值