对象内存模型的必要
当提起int,short,long,double和float等类型时,我们已经牢记了这些类型占用内存大小,占用几个字节,表示范围是多少,掌握了这些信息是编写程序最基本的要求,而且我们确实也需要知道在那种场景下用哪种类型。但是对于自定义的类型,我们很少去考虑它的内存占用空间和内存结构模型,有同学说:我不知道对象的内存模型,照样能把程序写的很好。但愿事情是这样的吧。但是我相信,知己知彼,才会百战不殆。
空的类
class CEmpty{
};
空的类对象大小不是0,是一个字节,即用一个char来表明自己的存在。
包含静态变量的类
class CAnimal {
public:
CAnimal();
~CAnimal();
private:
int m_member;
static int m_stcMem;
};
类的静态变量属于类的特性,不属于某一个对象。所以,在对象的模型中是找不到m_stcMem的,因此CAnimal 对象的大小为4字节,模型示意如下:
class CAnimal size(4):
+---
0 | m_member
+---
支持多态的类
多态是C++的基本特性之一,我们这里只讨论与对象模型相关的动态属性的多态(静态属性的多态为:函数重载和函数模板)深入理解对象的模型构造才能理解这一特性实现的原理。
class CAnimal {
public:
CAnimal() {}
~CAnimal() {}
public:
virtual void Run() = 0;
private:
int m_member;
};
类的多态是由virtual函数来实现的,更确切地说是由virtual机制来实现的,当类的函数具有这种vitual特性以后,所对应的对象(虽然这个CAnimal不能实例化,当然不能实例化,你见过一个动物叫‘Animal’吗)结构以及大小是怎样的呢?看下图:
//CAnimal Object
class CAnimal size(8):
+---
0 | {vfptr}
4 | m_member
+---
//Virtual function table
CAnimal::$vftable@:
| &CAnimal_meta
| 0
0 | &CAnimal::Run
可以看到:CAnimal的对象大小变成了8字节:一个成员变量m_member(4字节),一个vfptr指针(4字节:一个指针,不管它指向哪一种数据类型,指针本身所占用的内存是固定的)。vfptr指针指向了一个CAnimal::$vftable@ 称之为virtual function table(虚函数表)这个表存储了CAnimal内所有virtual的函数,这个表既然不占用对象的空间,它存储在哪里?
由上面对象模型可以看到,生成类对象的时候,编译器会将类对象的前四个字节设置为虚表的地址,而这四个字节就是一个指向虚函数表的指针,我们可以通过这个地址找到虚函数表的位置。由此看来虚函数表属于类,类的所有对象共享这个类的虚函数表。
基类的对象结构就是这样。但是,派生子类的对象内部是什么样的结构呢
class CAnimal {
public:
CAnimal() {}
~CAnimal() {}
public:
virtual void Run() = 0;
virtual void Eat() = 0;
private:
int m_member;
};
class CDog : public CAnimal {
CDog() {}
~CDog() {}
public:
void Run() {}
void Eat() {}
virtual void Sleep() {}
private:
int m_dogMember;
};
首先可以肯定的是,子类对象中肯定有父类的特征,子类所具有的自己“独创”的virtual函数,应该也会加入到虚函数表中,以此支持自己后代的多态性。如下图:
//CDog Object
class CDog size(12):
+---
0 | +--- (base class CAnimal)
0 | | {vfptr}
4 | | m_member
| +---
8 | m_dogMember
+---
//Virtual function table
CDog::$vftable@:
| &CDog_meta
| 0
0 | &CDog::Run
1 | &CDog::Eat
2 | &CDog::Sleep
类的继承体系当中还有一种情形是:虚继承。这里的“虚”,其实理解为"共享"更好一些,就是在对象模型当中,虚继承的基类被作为共享目标而存在。下面是简单的代码:
class CAnimal {
public:
CAnimal() {}
~CAnimal() {}
public:
virtual void Run() = 0;
virtual void Eat() = 0;
private:
int m_member;
};
class CWolf : virtual public CAnimal {
public:
CWolf() {}
~CWolf() {}
public:
virtual void Run(){}
virtual void Eat(){}
};
发生virtual派生之后,对象模型则是:
//CWolf Object
class CWolf size(16):
+---
0 | {vbptr}
+---
4 | (vtordisp for vbase CAnimal)
+--- (virtual base CAnimal)
8 | {vfptr}
12 | m_member
+---
//Virtual base table
CWolf::$vbtable@:
0 | 0
1 | 8 (CWolfd(CWolf+0)CAnimal)
//Virtual function table
CWolf::$vftable@:
| -8
0 | &(vtordisp) CWolf::Run
1 | &(vtordisp) CWolf::Eat
可以比较一下,虚继承发生时,对象中多了一个vbptr,注意是vbptr(虚基类指针),不是之前说到的vfptr(虚函数表指针)。前面我们说了,virtual继承可以理解为“共享”,"共享"的是什么呢,当然是Base 类 。我们可以从下面这些代码看到这样设计的原因。.
class CAnimal {
public:
CAnimal() {}
~CAnimal() {}
public:
virtual void Run() = 0;
virtual void Eat() = 0;
private:
int m_member;
};
class CWolf : virtual public CAnimal {
public:
CWolf() {}
~CWolf() {}
public:
virtual void Run(){}
virtual void Eat(){}
virtual void WolfEat() {}
private:
int m_wolf;
};
class CDog : virtual public CAnimal {
public:
CDog() {}
~CDog() {}
public:
virtual void Run() {}
virtual void Eat() {}
virtual void DogEat() {}
private:
int m_dog;
};
class CWolfDog : public CWolf, public CDog{
public:
CWolfDog() {}
~CWolfDog() {}
public:
virtual void Run() {}
virtual void Eat() {}
void WolfDogEat() {}
};
简单来说CWolfDog 类的两个父类CWolf 和CDog有一个共同的父类CAnimal ,这个时候我们来看CWolfDog对象中CWolf 和CDog对应的结构是怎么表示他们共同的Parent。对象模型如下:
//CWolfDog Object
class CWolfDog size(36):
+---
0 | +--- (base class CWolf)
0 | | {vfptr}
4 | | {vbptr}
8 | | m_wolf
| +---
12 | +--- (base class CDog)
12 | | {vfptr}
16 | | {vbptr}
20 | | m_dog
| +---
+---
24 | (vtordisp for vbase CAnimal)
+--- (virtual base CAnimal)
28 | {vfptr}
32 | m_member
+---
//Virtual function table
CWolfDog::$vftable@CWolf@:
| &CWolfDog_meta
| 0
0 | &CWolf::WolfEat
CWolfDog::$vftable@CDog@:
| -12
0 | &CDog::DogEat
//Virtual Base table
CWolfDog::$vbtable@CWolf@:
0 | -4
1 | 24 (CWolfDogd(CWolf+4)CAnimal)
CWolfDog::$vbtable@CDog@:
0 | -4
1 | 12 (CWolfDogd(CDog+4)CAnimal)
//Virtual function table
CWolfDog::$vftable@CAnimal@:
| -28
0 | &(vtordisp) CWolfDog::Run
1 | &(vtordisp) CWolfDog::Eat
确实,在CWolfDog对象中只存在一个CAnimal的结构,但是,它的名称变成了virtual base CAnimal,我的理解是:这是在标明这是一个共享的CAnimal结构。(我们还注意到这时的CAnimal前面还有一个修饰语vtordisp,对此MSDN给出的解释是:虚继承中派生类重写了基类的虚函数,并且在构造函数或者析构函数中使用指向基类的指针调用了该函数,编译器会为虚基类添加vtordisp域先忽略吧) 而CWolf和CDog的基类都用一个vbptr,这个指针指向了共享的CAnimal结构,以此来避免了对象调用时的选择恐惧症(恐怖菱形二义性Ambiguous)。