当调用一个虚函数时,一般都是使用了virtual table和virtual table pointer,简称vtbl和vptr:
一个函数指针数组。
在程序中,一个类凡是声明或继承了虚函数,都有一个vtbl,是指向虚函数实现体的指针。例如:
class C1
{
public:
C1();
virtual ~C1();
virtual void f1();
virtual int f2(char c) const;
virtual void f3(const string& s);
void f4() const;
...
};
//虚函数按照其声明顺序放于vtbl表中
//vtbl数组中的每一个元素对应一个函数指针指向该类的虚函数
//如果C2继承自C1,重新定义某些继承而来的虚函数,并加入新的虚函数:
class C2 : public C1
{
public:
C2(); //非虚函数
virtual ~C2(); //重定义函数
virtual void f1(); //重定义函数
virtual void f5(char* str); //新的虚函数
...
};
//此时,C2的vtbl包括没有被C2重定义的C1虚函数的指针f2、f3
凡是声明了虚函数的类的对象都含有一个隐藏的数据成员,指向对应的类的virtual table,称为vptr。
虚函数的第一个成本:
你必须为每个拥有虚函数的类耗费一个virtual table空间,其大小视虚函数的个数而定,包括继承而来的。
虚函数的第二个成本:
你必须在每一个拥有虚函数的对象内付出“一个额外指针”的代价。
虚函数的第三个成本:
你放弃了使用内联函数。
每个对象含有多个vptr,针对不同的父类vtbl,子类产生一个特殊的vtbl。
D->B->A,D->C->A,会导致A的字段在D中有两份,这显然不合理。为了解决这个问题,使用虚拟继承:B,C虚继承A,大多数编译器会利用这样的机会来减少编译器的负担。。
RTTI的设计理念是:根据class的vtbl来实现。
理解虚函数、多重继承、虚基类及RTTI的成本很重要。
但你必须知道:如果你需要这些功能,就要忍受这些成本,世事难两全。有时你确实想要回避编译器生成的服务,例如隐式vptr和“指向虚基类”的指针会造成“将C++对象存储于数据库”和“跨进程移动C++对象”困难度提高,所以你希望有某种方法模拟这些性质。不过从效率上来讲,自己写的不可能比编译器生成的更好。