关于继承,多重继承,以及虚拟类

来源:互联网 发布:unity3d烘焙光照贴图 编辑:程序博客网 时间:2024/04/28 23:58

class base
{
public:
   int i;
   virtual void a(void) = 0;
private:
   int j;
};

class de1 : public base
{
void a(void){}
};

class de2 : public base
{
void a(void){}
};

class dede : public de1,public de2
{

};

int main(void)
{
cout<<sizeof(dede)<<endl;
return 1;
}

输出24,也就是继承自de1有12个字节,继承自de2有12个字节。

base有12个字节,分别是i,j,vtbl(虚函数表指针)。所以继承是继承所有的父类的东西,包括private的,只是不能访问而已。

两个父类都有变量i,继承两个父类的子类也有一个变量i,这个子类的存储空间里就有3个i。

对于vtbl,抽象基类(base)有,子类(比如de1,de2)自己是没有的,只有继承自抽象基类的那个vtbl。dede分别继承de1,de2各一个vtbl,就有两个vtbl了。某一指针指向dede对象,根据指针类型选择vtbl,如果指针是dede或de1,则选择de1的那个vtbl;如果是de2型的指针,调用虚函数时就会选择de2的vtbl。

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

虚拟类

int main(void)
{
dede aa;

aa.i = 10;
}

问题出现在上面main函数中出现的三处aa.i ,因为dede同时继承了de1和de2,而这两个类都继承base,这就意味着会有两个base拷贝出现在dede的对象aa中。所以aa对象中会有两个i,那么aa.i=10中的i是de1对象中的,还是de2对象中的,程序不知道。所以这样写是不对的,应该写成:aa.de1::i=10或aa.de2::i=10,aa.de1::i理解方式为aa.(de1::i)。

为了避免上述的错误,可以继承虚基类。

class base
{
public:
   int i;
   virtual void a(void) = 0;
private:
   int j;
};

class de1 : public virtual base
{
void a(void){}
};

class de2 : public virtual base
{
void a(void){}
};

class dede : public de1,public de2
{

};

 

这样再使用aa.i就不会有问题了。通常,基类和虚基类之间的唯一区别,体现在对象不止一次继承基类的情况下。如果使用虚基类,那么对象中只出现一个基类,否则,就会出现多个拷贝。假如这样:
class a {};
class b: public a {};
class c: public b, public a {};
类c就有两个a部分了,一个来自类b,一个直接来自类a
a a
| |
| b
| |
/ /
c
用虚拟基类就不会,而是共用一个a
a
/ /
| b
/ /
c

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

多重继承
class base
{
public:
   int i;
   virtual void a(void) = 0;
private:
   int j;
};

class de1 : public base
{
public:
// void a(void){}
int s;
};

class de2 : public base
{
public:
// void a(void){}
int s;
};

class dede : public de1,public de2
{
public:
void a(void){}
int s;
};

int main(void)
{
cout<<sizeof(base)<<endl;
cout<<sizeof(de1)<<endl;
cout<<sizeof(de2)<<endl;
cout<<sizeof(dede)<<endl;


de1* p1;
de2* p2;
dede* p3;
dede aa;

p1 = &aa;
p2 = &aa;
p3 = &aa;

// aa.s = 1;
return 1;
}

运行后调试发现,p1地址是0x0012ff28,p2地址是0x0012ff38,p3地址是0x0012ff28。

正好de1和de2在dede类中,按次序排好。de1占了前16个字节,所以p2指向p1+16。
根据c++对象成员的布局 我们知道 de1占据起始地址, de2 在它下面,因此 p2指向的地址必须进行调整。
编译器会生成以下代码:
p2 = p3+sizeof(p1);
如果要析构 p2指向的对象 地址仍然需要调整。

这样通过p2指针只能访问dede中de2的那一块内存,成员变量(通过指针访问的话,只能访问de2的成员,而不是dede的所有成员)是可以这样通过限制在这块内存内实现访问,函数就是通过vtbl实现访问了。

类似于

typedef struct
{
int a;
char b;
int c;
} A;
typedef struct
{
int t;
char y;
} B;
int main(void)
{
A te1;
B* pb;
pb = &te1;
}
这样pb就可以访问A的前两个变量。

 

但是又发现 dede aa;中de1内存块和de2内存块中vtbl所指向的虚函数表内,函数a的地址是不同的:

de1:   [0] 0x00411168 dede::a(void) *
de2:   [0] 0x00411118 [thunk]:dede::a`adjustor{16}' (void) *
照理说应该是都是指向dede::a这个函数,应该相同才对。Bjarne 采用的 称之为adjustor 在虚函数表中不光存储虚函数地址,而且存储对应对象的位移。

 由上面汇编代码可以看出,"thunk"代码并不是那么神秘,它只是简单的将寄存器的值加16(de1的虚函数乘以所占dede虚函数表中虚函数个数),然后跳转到另外一个函数。结合着本文中的类的继承层次关系,我开始慢慢的明白了"thunk"的任务。在多继承的情况下,各基类指针的值应该是不一样的,只有第一个基类的指针值和派生类类对象的首地址是一致的,其他的基类指针和派生类对象的首地址存在一个偏移。假如多个基类也都从一个共同的基类继承而来,理论上说我们可以通过任何一个基类指针去调用这个共同基类的虚函数,这个虚函数调用会被解析到派生类的虚函数实现,而且派生类也只能有一个虚函数实现。为了使通过任何一个基类指针调用的虚函数都调用同一个函数,我们只需要将这样的虚函数调用"转化"到通过第一个基类指针来调用就可以了,而在第一个基类的虚表中存放虚函数的实现。这个转化的过程就是由"thunk"来完成的:它首先将基类指针调整到第一个基类的地址,也就是派生类对象的首地址,然后调用相应的虚函数。

这里不是很正确   详见  C++多态汇编分析    http://blog.csdn.net/lqk1985/archive/2008/01/27/2067817.aspx  3.1:从内存中得到分析所用的一些有用的值:   

有两种thunk:

第一种,所有虚函数的直接取地址,取到的都是thunk代码的地址(真实的函数地址在虚函数表中),都是第一步调用thunk代码,来转到虚函数表。

第二种,thunk是派生类实现了基类中的同名函数,则第二个基类的虚函数在派生类的虚函数表中位置在第一个基类的位置之上。然而第二个基类中与第一个基类中调用相同函数代码的同名函数在虚函数表中不放真实的函数地址,放的一段thunk代码的地址,这段thunk代码再把此this指针改成当真正实现此函数的类在此派生类中的位置(不是虚函数表中函数地址的位置),然后通过jmp直接调用。

 

我再加上一个与de1,de2类似的de3,指向dede对象的de3指针的adjustor{32}。就是相对于首地址的位移有32。这就是多个基类(de1,de2,de3)从一个基类(base)继承过来时,de2、de3的虚函数调用都可一偏移到de1来调用。

但是如果这几个基址不是从同一个基类继承呢,也就是他们的vtbl中含有的虚函数都是不一样的。那样应该不存在adjustor了。我把de3的继承基类改成含有一个函数b的base2。dede:public de1,de3,de2    把de3放在了de2的前面,调试发现de3里面vtbl中包含的是直接的函数b的地址,没有adjustor了。de2的adjust是32,也就是偏移了de1和de3的内存大小。

但是,如果de3继承自与de1不同的基类,但是两个基类(base与base2)所含的函数名相同的话,de3还是有adjustor的,所以编译器是根据函数名来判断是否需要adjustor的。

每个基类的vtbl中的函数地址 要通过thunk代码adjustor(偏移)   到 第一个出现拥有与本虚函数同名的基类所占内存处(并不一定就在派生类的第一个基类内存处,可能第N个)。如果没有同名就直接写派生类的函数地址。

 

 

 

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 

 

#include <iostream>
using namespace std;
/*
class base
{
public:
int i;
};
class derived1:public base
{
public:
int j;
};
class derived2:public base
{
public:
int k;
};
class derived3:public derived1, public derived2
{
public:
int sum;
};

int main(void)
{
derived3 ob;
ob.derived2::i = 10;
ob.j = 20;
ob.k = 30;
ob.sum = ob.derived2::i+ob.j+ob.k;
cout<<ob.derived2::i<<endl<<ob.j<<endl<<ob.k<<endl<<ob.sum<<endl;
}*/

class base
{
 public:
  int i;
  virtual void a(void) = 0;
 private:
  int j;
};

class base2
{
 public:
  int i;
  virtual void b(void) = 0;
 private:
  int j;
};


class de1 : public base
{
public:
// void a(void){}
 int s;
};

class de2 : public base2
{
public:
// void a(void){}
 int s;
};

class de3 : public base2
{
public:
// void a(void){}
 int s;
};


class dede : public de1,public de2,public de3
{
public:
 void a(void){}
 void b(){}
 virtual void c(void) {cout<<"dede c"<<endl;}
 virtual void d(void)=0; //{cout<<"dede d"<<endl;}
 int s;
};

class dedede : public dede
{
public:
 void c(void){cout<<"dedede c"<<endl;}
 void d(void){cout<<"dedede d"<<endl;}
};


//////////////////////////////////////////////////////////

class withv
{
public:
 virtual void a(void){}
};

class nonv
{
public:
 int s;
};

class denonv1:public nonv
{
 virtual void e(void){}
};

class denonv2:public nonv,public withv
{
 void a(void){}
 virtual void e(void){}
};


//////////////////////////////////////////////////////////


int main(void)
{
 cout<<sizeof(base)<<endl;
 cout<<sizeof(de1)<<endl;
 cout<<sizeof(de2)<<endl;
 cout<<sizeof(dede)<<endl;
 cout<<sizeof(dedede)<<endl;

/*
 de1* p1;
 de2* p2;
 de3* p3;
 dede* p4;
 dede aa;
 

// Pdedea pfna = &dede::a;


 p1 = &aa;
 p2 = &aa;
 p3 = &aa;
 p4 = &aa;*/
///////////////////////////////////////////////////////////////////////
 dedede bb;
 dedede * p6 = &bb;


//////////////////////////////////////////////////////////////////////
 typedef void (de1::* Pde1a)();
 typedef void (de2::* Pde2b)();
 typedef void (de3::* Pde3b)();
 typedef void (dede::* Pdedea)();
 typedef void (dede::* Pdedeb)();

 typedef void (dede::* Pdedec)();
 typedef void (dedede::* Pdededec)();

 typedef void (dede::* Pdeded)();
 typedef void (dedede::* Pdededed)();

 Pde1a pfde1a = &de1::a;
 Pde2b pfde2b = &de2::b;
 Pde3b pfde3b = &de3::b;
 Pdedea pfdedea = &dede::a;
 Pdedeb pfdedeb = &dede::b;

 Pdedec pfdedec = &dede::c;
 Pdededec pfdededec = &dedede::c;

 Pdeded pfdeded = &dede::d;
 Pdededed pfdededed = &dedede::d;
//////////////////////////////////////////////////////////////////

 denonv1 cc;
 denonv1* pnonv1 = &cc;
 cout<<sizeof(denonv1)<<endl;

 denonv2 dd;
 denonv2* pnonv2 = &dd;
 cout<<sizeof(denonv2)<<endl;
 cout<<&cc<<endl;
 cout<<&cc.s<<endl;

 withv* pw;
 pw = &dd;
 pw->a();
// aa.s = 1;
 return 1;
}


原创粉丝点击