摘要:C++面向对象模型的主要特征是:(1)类和封装性(2)继承性(3)多态性。多态性在前两者和虚函数的基础上实现,而虚函数正是实现面向对象的核心机制。
关键字:虚函数 继承 多态
在面向对象的程序设计中,系统被看成由多个对象组成,通过对象之间的通信形成了系统。其主要特征是:(1)类和封装性(2)继承性(3)多态性。多态性在前两者和虚函数的基础上实现,而虚函数正是实现面向对象的核心机制。
1.虚函数与Vptr、Vtbl 在C++中,有两种数据成员: 静态的(static)和非静态的(nonstatic),有三种成员函数: 静态函数、非静态函数和虚函数。在Stroustrup当初设计的C++模型中,非静态数据成员被存放在每一个对象之内,静态数据成员则被存放在所有的对象之外。静态成员函数和非静态成员函数也被存放在所有的对象之外。虚函数则以两个步骤来支持:
每一个类产生出一队指向虚函数的指针,放在一个表格之中。这个表格被称为虚函数表(Vtbl)。
- 每一个对象被编译器添加了一个指针,指向相关的虚函数表。通常这个指针被称为Vptr。Vptr的设置(setting)和重置(resetting)由每一个类的构造函数(constructor)、析构函数(destructor)和拷贝赋值(copy assignment)运算符自动完成。每一个类所关联的type_info object(用以支持运行时类型识别,即RTTI)也通过虚函数表指出来,通常放在虚函数表的第一个记录中。
每一个类产生出一队指向虚函数的指针,放在一个表格之中。这个表格被称为虚函数表(Vtbl)。
- 每一个对象被编译器添加了一个指针,指向相关的虚函数表。通常这个指针被称为Vptr。Vptr的设置(setting)和重置(resetting)由每一个类的构造函数(constructor)、析构函数(destructor)和拷贝赋值(copy assignment)运算符自动完成。每一个类所关联的type_info object(用以支持运行时类型识别,即RTTI)也通过虚函数表指出来,通常放在虚函数表的第一个记录中。
举例说明,有class A定义如下:
class A {
private :
int value;
public:
virtual void Func1(void)
virtual void Func2(void)
};
class A的内存布局如下:(为简化说明,将虚函数表中第一个记录的type_info object略去)
图2-1 类A的对象内存图
2.继承与多态
class B继承于class A,定义如下:
class B : pulic A {
private :
int value1;
public:
virtual void Func1(void)
virtual void Func2(void)
};
class B的内存布局如下:
图2-2 类B的对象内存图
假设有如下一段代码:
A objectA;
B objectB;
A *pA = &objectA;
pA->Func1();
pA->Func2();
pA = &objectB;
pA->Func1();
pA->Func2();
第一个pA->Func1()语句实际调用的是A::Func1(),编译器会将它转换为( * pA->vptr[1] )( this ),pA->vptr[1]即A::Func1,而第二个pA->Func1()语句调用B::Func1(),编译器同样将它转换为( * pA->vptr[1] )( this ),只是此时pA->vptr[1]已经关联到B::Func1。第一个pA->Func2()语句实际调用A::Func2(),编译器将它转换为( * pA->vptr[2] )( this ),pA->vptr[2]即A::Func2,而第二个pA->Func2()语句调用B::Func2()。
在继承和虚函数的基础上,C++实现了多态。
3. 多重继承
class C、class D、class E定义如下:
class C {
private :
int value2;
public:
virtual void Func3(void)
virtual void Func4(void)
};
class D : public C {
private :
int value3;
public:
virtual void Func3(void)
};
class E : public B, public D {
private :
int value4;
public:
virtual void Func1(void)
virtual void Func4(void)
};
class A 、class B、class C、class D、class E的关系如图所示:
图2-3 类A、B、C、D、E的继承关系图
图2-4 类E的对象内存图
类E对象的内存布局中有两个vptr,第一个派生自类B,第二个派生自类D。通过指向第二或后继的基类的指针来调用派生类的虚函数,需要在运行时以适当的偏移值(offset)调整this指针,以使指向类C的对象或类D的对象的指针能正确调用D::Func3和E::Func4。
比较有效率的解决方法是利用thunk技术,thunk是一小段汇编(assembly)代码,用来以适当的偏移值调整this指针,并跳转到相应的虚函数处。例如,通过一个指向类D的对象的指针调用Func4(pD->Func4),其相关thunk的类C代码如下所示:
this += sizeof( B );
E::Fun4( this );
参考:
-
潘爱民COM讲义
-
Stanley Lippman,《Inside C++ Object Model》