虚成员函数是动态确定的(在运行时)。也就是说,成员函数(在运行时)被动态地选择,该选择基于对象的类型,而不是指向该对象的指针/引用的类型。这被称作“动态绑定”。大多数的编译器使用以下的一些的技术:如果对象有一个或多个虚函数,编译器将一个
隐藏的指针放入对象,该指针称为“virtual-pointor”或“v-pointer”。这个v-pointer指向一个全局表,该表称为“虚函数表(virtural-table)”或“v-table”。
编译器为每个含有至少一个虚函数的类创建一个v-table。例如,如果Cirle类有虚函数ddraw()、move() 和 resize(),那么将有且只有一个和Cricle类相关的v-table,即使有一大堆Circle对象。并且每个 Circle对象的 v-poiner将指向 Circle的这个 v-table。该 v-table自己有指向类的各个虚函数的指针。例如,Circle 的v-table 会有三个指针:一个指向Circle::draw(),一个指向 Circle::move(),还有一个指向Circle::resize()。
在分发一个虚函数时,运行时系统跟随对象的 v-pointer找到类的 v-table,然后跟随v-table中适当的项找到方法的代码。
以上技术的空间开销是存在的:每个对象一个额外的指针(仅仅对于需要动态绑定的对象),加上每个方法一个额外的指针(仅仅对于虚方法)。时间开销也是有的:和普通函数调用比较,虚函数调用需要两个额外的步骤(得到v-pointer的值,得到方法的地址)。由于编译器在编译时就通过指针类型解决了非虚函数的调用,所以这些开销不会发生在非虚函数上。
举个例子:
class Class1 {
public :
data1;
data2;
memfunc();
virtual vfunc1();
virtual vfunc2();
virtual vfunc3();
};
Class1 物件實體在記憶體中佔據這樣的空間:
每一個由此類別衍生出來的物件,都有這麼一個 vptr。當我們透過這個物件呼叫虛擬函
式,事實上是透過 vptr 找到虛擬函式表,再找出虛擬函式的真正位址。
奧妙在於這個虛擬函式表以及這種間接呼叫方式。虛擬函式表的內容是依據類別中的虛
擬函式宣告次序,一一填入函式指標。衍生類別會繼承基礎類別的虛擬函式表(以及所
有其他可以繼承的成員),當我們在衍生類別中改寫虛擬函式時,虛擬函式表就受了影
響:表中元素所指的函式位址將不再是基礎類別的函式位址,而是衍生類別的函式位址。
看看這個例子:
class Class2 : public Class1 {
public :
data3;
memfunc();
virtual vfunc2();
};
於是,一個「指向 Class1 所生物件」的指標,所呼叫的 vfunc2 就是 Class1::vfunc2,而
一個「指向 Class2 所生物件」的指標,所呼叫的 vfunc2 就是 Class2::vfunc2。
動態繫結機制,在執行時期,根據虛擬函式表,做出了正確的選擇。