您的位置:首页 > 编程语言 > C语言/C++

深入理解C++面向对象机制(一)多继承

2014-08-16 14:51 513 查看

深入理解C++面向对象机制(一)多继承

零.声明

1.《深入理解C++面向对象机制》系列的博文是博主阅读《深度探索C++对象模型》之后的自我总结性质的文章。当然也希望这些文章能够帮助那些想深入了解C++的网友。

2.文章中会有一些被称为“编译器生成的代码”,这些代码并不是编译器真正的生成代码,只是为了方便讨论而写的模拟代码。

3.如果觉得文章对你有帮助而需要转载,也请阁下能够注明出处。

4.如果觉得博文对问题的讨论有误,也可以给博主留言。

一.引入

我们在《深入理解C++面向对象机制(零)单继承》中讨论了C++的面向对象的一个核心特性多态。在单继承下的环境下,我们理解了C++是如何实现虚函数的。本文将继续讨论,请情况扩充到多继承。多继承相对于单继承,多了一项工作,this指针的调整。

二.多继承下的虚函数

1. this调整

我们先来看一个普通的多继承情况,我们为什么要调整this指针。

class CDerived : public CBase1, CBase2
CBase2 * p = new CDerived;


编译器对多继承类的存放方式就如下图。



图1.0

类CDerived的对象中先存放是第一个基类CBase1的subobject的数据,然后接着的是第二个基类的subobject。最后才是CDerived新定义的数据。
所以上面的两行代码在编译器看来可能会变成这样:

CDerived * pTemp = __new(sizeof(CDerived));   //1
pTemp = CDerived::CDerived(pTemp);            //2
CBase2 * p = pTemp ? pTemp + sizeof(CBase1) : 0;//3
//析构函数会在__delete之前调用,但是这里先不做讨论
__delete(p ? p - sizeof(CBase1) : 0);        //4

第一行和第二行代码,就是编译器为CDerived对象分配内存并调用构造函数;
第三行代码,pTemp是指向CDerived对象的开始处,但是CBase2 *p指针需要调整pTemp,移动sizeof(CBase1)才能到CBase2的subobject。这里就是在this指针的调整。
第四行代码,delete对象,我们需要将指针p移回CDerived的开始处。
假如在CDerived对象调用它的虚函数,需要传入this指针的时候,也是要考虑this指针的调整。

2.Thunk技术

class CBase1
{
public:
CBase1();
virtual ~CBase1();
private:
int m_x;

public:
virtual void Fun();
virtual void Fun1_1();
virtual void Fun1_2();
void Fun1_3();
};

class CBase2
{
public:
CBase1();
virtual ~CBase1();
private:
int m_y;
public:
virtual void Fun();
virtual void Fun2_1();
virtual void Fun2_2();
void Fun2_3();
};

class CDerived : public CBase1, CBase2
{
public:
CDerived();
~CDerived();
private:
int m_z;
public:
virtual void Fun();
virtual void Fun1_1();
virtual void Fun2_1();
};


按照之前的讨论方式,接下来就是这几个类的virtual table图。



图1.1

图1.1中展示了两个基类的virtualtable图。

现在我们介绍Thunk技术。从图1.1可以看出,CDerived类的第一个virtual table,明显比第二个大。而第二个virtual table其中的几个函数就是CBase2的virtual table那几个函数,只是其中在几个函数(析构函数、Fun和Fun2_1)被替换成了CDerived中定义的函数。

再来看一个表,里面不光包含了CBase1的几个函数也包括了CBase2的函数。

从这两个表就可以大致看出Thunk的做法,第一个表作为主表,而后的表作为次表。主表包含CDerived所要用到的所有虚函数的地址,次表则包括第二个基类所要用到的虚函数地址(如果有第三个基类,就会第二张次表)。

接下来就要看一下,不管主表还是次表都有一个槽是被涂成了深灰色。这几个槽中的虚函数,就需要调整this指针的。

3.其他的技术

除了上面的Thunk技术,还有一种简单明了的办法。

在virtual table中存放一个虚函数指针(pFun),还存放一个this指针的调整值(nOffset)。

比如下面代码:

CBase2 * p = new CDerived;
p->Fun();
编译器会将第二行代码解释成下面这样:

(*p->vptr[2].pFun)(p+ p->vptr[2].nOffset);

这种方法比较好理解。但是这样有一个坏处,就是每一个虚函数槽都要放一个offset,即使那些不需要this指针调整。这就造成了不必要的浪费。

三.结尾

现在我们了解了多继承下的虚函数实现方式。单继承延生至多继承,就是如何高效地解决this指针调整的问题。接下来将会讨论虚拟继承(一种比较特殊的继承方式)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: