灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:3082回复:0

[C++技术]C++对象模型之虚函数实现原理

楼主#
更多 发布于:2013-05-13 10:03

在C++中,多态(polymorphism)的意思是,用基类的指针或者引用,寻址出一个派生类对象。而虚函数(virtual member function)是多态的基础,这也是面向对象编程迷人之处。现在刚好有时间,就写一下自己对C++在单一继承情况下如何实现虚函数的肤浅认识。

一旦一个类有一个虚函数,那么一定会建立一个虚表(virtual table),虚表里面有所有虚函数的地址。虚表是该类所有对象共享的,每个对象都有一个虚指针(vptr),指向虚表。虚指针的设定与重置,是由类的构造函数与析构函数以及复制运算符(copy assignment)自动完成的。

虚表是虚函数的基础。表格有很多槽(slot),通常第一个槽位存放的是每一个类所关联的类型信息(type_info object),用来支持运行时类型识别(RTTI)。

[cpp]

class Demo{

public:

    Demo();

    virtual ~Demo();

    virtual void f1();

    virtual void f2();

    void f3();

    static void f4();

private:

    static int ival;

    long lval;

};

class Demo{

public:

 Demo();

 virtual ~Demo();

 virtual void f1();

 virtual void f2();

 void f3();

 static void f4();

private:

 static int ival;

 long lval;

};

图1 Demo类的对象内存布局

每个对象内部都会存储非静态成员变量以及虚指针,其它成员都会在对象外部存储,这些不是这次要讨论的内容。

在C++中,虚函数在编译器间获知,而且这些虚函数的地址是固定不变的,执行期不可能新增或者被替换。为了找到函数的地址,每一个虚函数都会有一个在虚表里面的索引号。

如果继承基类的过程中,派生类决定不改写某个虚函数,那么它就会继承基类这个函数的实体(定义),此时,在这个派生类的虚表中存放的就是基类的该函数。否则,如果派生类重写了基类的某个虚函数,那么派生类的虚表中存放的就会是对此虚函数重新定义的实体。如下图所示。

[cpp]

class Base{

public:

    virtual void x();

    virtual void y();

    int ival;

};

//单一继承  

class Derived: public Base{

public:

    void x();

    virtual void z();

    long lval;

};

class Base{

public:

 virtual void x();

 virtual void y();

 int ival;

};

//单一继承

class Derived: public Base{

public:

 void x();

 virtual void z();

 long lval;

};

图2 单一继承情况下的虚表

特别要注意的是:

1)如果是继承基类声明的虚函数实体,那么该函数实体就会被拷贝到派生类虚表相对应的槽位上。

2)如果是使用自己的函数实体,也就是改写基类的虚函数,那么也必须把这个函数实体地址放到相对应的槽位上。

3)如果是新增加一个虚函数,那么虚表就会增加一个slot,并把新的虚函数实体放进这里。

所以,假设对于上面的例子,我们有这样的用法:

[cpp]

ptr->x();

ptr->x();我们不知道ptr是Base还是Dervied指针,也就无法由ptr取得该对象的虚表,但是,我知道每一个x()函数的地址都会放在#1槽位上,所以,编译器会将该调用转换成

[cpp]

(*ptr->vptr[1])(ptr);

(*ptr->vptr[1])(ptr);上面的表达式中,唯一需要在执行期才能知道的东西是,槽位1是哪个类虚表的,一旦知道了,那么便可调用相应的函数体了。

就写到这。

多重继承与虚拟继承下,虚函数的实现就没那么容易了。下次有空写一下。


喜欢0 评分0
游客

返回顶部