推荐《Inside The c++ Object Model 》, 文章转自其中一段。c++
多态是面向对象的一个重要特征,c++中多态是经过虚函数机制实现的,关于c++多态实现的一些基本知识,本文就不在细述。ide
一般相似这样:函数
Shape * ps = new circle;布局
ps->Rotate(); //调用的是虚函数设计
虽然ps是shape类型指针, 可是调用的是circle中的Rotate方法。这是毫无疑问的, 这样作会很易于咱们程序的封装。多态的主要用途是经由一个共同的接口来影响类型的封装, 这个接口一般被定义在一个抽象的base class中。一个指针, 无论指向哪种类型数据在咱们的机器上他自己所须要的内存大小是固定的,如16位机器上是2byte,32位机器上是4byte,咱们使用指针能够调用对象函数、成员, 是由于咱们知道这个类型对象在内存中所占的区域大小, 经过指针天然能找到其中的成员地址以及虚函数列表指针。举个列子,下面有个ZooAnimal声明:指针
class ZooAnimal {对象
public:blog
ZooAnimal();继承
virtual ~ZooAnimal();接口
//...
virtual void rotate();
protected:
int loc;
String name;
};
ZooAnimal za("Zoey");
ZooAnimal *pza = & za;
其中class object za 和指针pza的可能布局如图下所示:
可是, 一个指向ZooAnimal的指针是如何的与一个指向整数的指针或template Array(以下, 与一个String一并产生)指针有所不一样呢?
ZooAnimal * px;
int *pi;
Array<String> * pta;
之内存需求的观点来讲, 没有什么不一样!他们三个都须要足够的内存来存放一个机器地址(32位为4个bytes)。“指向不一样类型之个指针”间的差别,既不在其指针表示法不一样,也再也不其内容(表明一个地址)不一样,而是在其所寻址出来的Object类型不一样。也就是说,“指针类型”会告诉编译器如何解释某个特定地址中的内存内容及其大小:
嗯, 那么, 一个指向地址1000而类型为void*的指针,将涵盖怎样的地址空间?是的, 咱们不知道!这就是为何一个类型为void的指针只可以含有一个地址,而不能经过它操做所指的object的缘故。
因此,转型(cast)实际上是一种编译器指令,大部分状况先他并不改变一个指针所含的真正地址, 它只影响“被指出以内存的大小和其内容”的解释方式。
如今,让咱们定义一个Bear, 做为一种ZooAnimal。固然,经由“public继承”能够完成这件任务:
class Bear:public ZooAnimal{
public :
Bear();
~Bear();
//..
void rotate();
virtual void dance();
protected:
enum Dances{...};
Dances dances_known;
int cell_block;
}
Bear b("Yogi");
Bear *pb = &b;
Bear &rb = *pb;
b、pb、rb会有怎样的内存需求呢?不论是pointer或者reference都只须要4个bytes(16位上2-bytes)空间。Bear Object须要24bytes, 也就是ZooAnimal的16bytes加上Bear所带来的8bytes,,如图下展现了可能的内存布局:
好, 假设,咱们的Bear Object放在地址的1000处, 一个Bear指针和一个ZooAnimal指针有什么不一样?
Bear b;
ZooAnimal *pz = &b;
Bear * pb = &b;
它们每一个都指向Bear Object的第一个byte。 其间的差异, pb所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear Object中ZooAnimal subObject 。
除了ZooAnimal subObject中出现的members, 你不能狗使用pz来直接处理Bear的任何members。惟一的列外是经过virtual机制:
//不合法:cell_block不是ZooAnimal的一个member
//虽然咱们知道pz当前指向衣蛾Bear Object。
pz->cell_block;
//ok: 通过一个明白的downcast操做就没问题
((Bear*)pz)->cell_block;
//下面这样更好, 但它是一个run-time operation(成本较高)
if (Bear* pb2 = dynamic_cast<Bear*>(pz))
pb2->cell_block;
//Ok, 由于cell_block是Bear的一个member
pb->cell_block;
当咱们写:
pz->rotate();
时,pz的类型将在编译时期决定如下的两点
1) 固定的可用接口。也就是说,pz只可以调用ZooAnimal的public接口
2) 该接口的access level(列如rotate()是ZooAnimal的一个public member)
在每个执行点, pz所指的object类型能够决定rotate()所调用的实体。类型信息的封装并非维护于pz之中,而是维护与link之中,此link存在于“Object的vptr” 和“vitual table”之间。
如今, 请看看这种状况:
Bear b;
ZooAnima za = b; //译注:这里会引发切割(sliced)
//调用ZooAnimal::rotate()
za.rotate();
为何rotate()所调用的是ZooAnimal实体而不是Bear实体? 此外,若是初始化函数(译注:应用与行数assignment操做发生时)将一个object内容完整拷贝到另外一个object中去, 为何za的vtr不指向Bear的virtual table?
第二个问题的答案是,编译器在(1)初始化(2)指定(assignment)操做(将一个class object指定给另外一个class object)之间作出了仲裁。编译器必须确保若是某个Object含有一个或一个以上的vptrs,那写vptrs的内容不会被base class object初始化或改变。
至于第一个问题的答案是:za并非(并且也毫不会是)一个Bear,它是(而且只能是)一个ZooAnimal。多态所形成的“一个以上的类型”的潜在力量,并不能实际发挥在“直接存取objects”这件事上。有一个似是而非的观念:OO程序设计并不支持对Object的直接处理。举个例子,下面这一组定义:
{
//注:Panda继承Bear ,Bear继承ZooAnimal
ZooAnimal za;
ZooAnimal *pza;
Bear b;
Panda *pp =new Panda;
pza = &b;
}
其可能的内存布局以下图:
将za或b的地址,或pp所含的内容(也是个地址)指定给pza, 显然不是问题。一个Pointer或一个reference之因此支持多台,是由于他们并不引起内存中任何“与类型有关的内存委托操做(type-dependent commitment)”;会受到改变的只是它们所指向的内存的“大小和内容解释方式”而已。
然而,任何人若是改变Object za的大小(或是被指定为)一个derived class Object时, derived object就会被切割, 以塞入较小的base type内存中, derived type将没有留下任何蛛丝马迹。多态因而再也不呈现,而一个严格的编译器能够再编译时期解析一个“经过该Object而触发的virtual function调用操做”,于是回避virtual机制。若是virtual function 被定义俄日inline,则更有效率上的大收获。
总而言之,多态是一种威力强大的设计机制,容许你继承一个抽象的public接口以后,封装相关的类型。然而须要付出的代价就是额外的间接性--不管是在“内存的得到”或是“类型的决断”上。c++经过class 的pointer和references 来支持多态,这种程序设计风格就是“面向对象”。