虚拟函数-2、实现机制

讲到这里,也许读者对虚拟函数还不能有一个清晰的认识。下面就对虚拟函数的实现机制作简要介绍。
2.3.1 类和对象的内存分配机制
首先应该了解类及对象的内存分配机制。如果有类的定义如下:
class CMem
{
public:
CMem();
public :
int m_first;
private:
unsigned char m_second;
public:
fun1();
};
则对象
CMem a, b;
的内存分配如图2-1所示。由图2-1可知,同一个类的所有对象共享同一个成员函数的地址空间,而每个对象有独立的成员变量地址空间。也可以这样说,成员函数是类拥有的,成员变量是类的对象拥有的。
图2-1 类及对象的内存分配
2.3.2 基类与派生类的内存分配关系
如果有类的定义如下:
class CMem
{
public:
CMem(){};
public :
int m_first;
private:
unsigned char m_second;
public:
fun1();
int funOver(){ return 1;};
};
 
class CMemSub :public CMem
{
public:
         CMemSub(){};
public:
         int m_three;
private:
         int m_four;
 
public:
         fun3();
int funOver(){return 2;};
};
则对象
CMem a;
CMemSub b;
的内存分配如图2-2所示。由图2-2可知,派生类对象的内存区域包含了父类所有的成员变量空间(如上例中的int m_first、int m_second),同时包含派生类定义的成员空间(如上例中的int m_three、int m_four)。其中,父类的成员变量空间被称为父类子对象,整个派生类实例的内存空间被称为全对象。但是由于编译器的限制,基类中那些没有被派生类继承的私有成员变量,派生类无法访问(如上例中的int m_second,不允许对象b访问)。对于从基类继承的成员函数,派生类和基类共享(如上例中的CMem()、fun1())。
图2-2 基类和派生类的内存分配

 

2.3.3 非虚拟函数的内存分配机制
对于非虚拟函数的调用,编译器只根据数据类型翻译函数地址,判断调用的合法性。因为对象的内存地址空间中只包含成员变量,并不存储有关成员函数的信息,所以非虚拟函数的地址翻译过程与其对象的内存地址无关。
2.3.2节中的类CMemSub重载了基类的funOver()成员函数,下面的程序段定义了一个CMem的指针*pMem,先后将它指向CMem和CMemSub对象的地址,并且调用成员函数funOver()。从这段程序的输出结果可以发现非虚拟函数的地址翻译特点。
{
 CMem a,*pMem;
 CMemSub b;
pMem=&a;
printf("%d\n",pMem->funOver());
pMem=&b;
printf("%d\n",pMem->funOver());
}
该程序段的输出结果:
1
1
因为pMem的类型是CMem指针,所以不论pMem指向什么对象的地址,pMem-> funOver()始终调用CMem:: funOver()成员函数。
2.3.4 深入:虚拟函数的内存分配机制
因为虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。原来,如果类中定义了虚拟函数,该类及其派生类就要生成一张虚拟函数表,即vtable。而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。
如果有类的定义如下:
class CMem
{
public:
         CMem(){};
public :
         int m_first;
private:
         unsigned char m_second;
public:
         fun1();
virtual int funOver(){return 1;}
};
class CMemSub :public CMem
{
public:
         CMemSub(){};
public:
         int m_three;
private:
         int m_four;
public:
         fun3();
virtual int   funOver(){return 2;};
virtual int   fun4(){return 3;};
};
则对象
CMem a;
CMemSub b;
的内存分配如图2-3所示。
图2-3 包含虚表入口的对象内存空间
如果还执行程序段:
{
 CMem a,*pMem;
 CMemSub b;
pMem=&a;
printf("%d\n",pMem->funOver());
pMem=&b;
printf("%d\n",pMem->funOver());
}
输出结果:
1
2
由图2-3不难发现虚函数的工作机制,由于对象的内存空间中包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。语句pMem=&b;使pMem指向对象b的内存空间,调用pMem->funOver()时,编译器得到了对象b的vtable入口,并由这个入口找到CMemSub::fun4()虚函数地址。
到此,虚拟函数的秘密终于大白天下了。虚函数是C++语法的重点和难点,如果读者还不甚明了,请对以上论述仔细揣摩。

posted on 2011-05-09 17:58  carekee  阅读(461)  评论(0编辑  收藏  举报