当前位置:编程学习 > C/C++ >>

C++对象模型之虚函数实现原理

在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是哪个类虚表的,一旦知道了,那么便可调用相应的函数体了。

 

就写到这。

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

 

补充:软件开发 , C++ ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,