C++面向对象模型

摘要:C++面向对象模型的主要特征是:(1)类和封装性(2)继承性(3)多态性。多态性在前两者和虚函数的基础上实现,而虚函数正是实现面向对象的核心机制。
关键字:虚函数 继承 多态

    在面向对象的程序设计中,系统被看成由多个对象组成,通过对象之间的通信形成了系统。其主要特征是:(1)类和封装性(2)继承性(3)多态性。多态性在前两者和虚函数的基础上实现,而虚函数正是实现面向对象的核心机制。

1.虚函数与Vptr、Vtbl

    在C++中,有两种数据成员: 静态的(static)和非静态的(nonstatic),有三种成员函数: 静态函数、非静态函数和虚函数。在Stroustrup当初设计的C++模型中,非静态数据成员被存放在每一个对象之内,静态数据成员则被存放在所有的对象之外。静态成员函数和非静态成员函数也被存放在所有的对象之外。虚函数则以两个步骤来支持:

  1. 每一个类产生出一队指向虚函数的指针,放在一个表格之中。这个表格被称为虚函数表(Vtbl)。

  2. 每一个对象被编译器添加了一个指针,指向相关的虚函数表。通常这个指针被称为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 );

参考:

  1. 潘爱民COM讲义
  2. Stanley Lippman,《Inside C++ Object Model》

没有更多推荐了,返回首页