在 C++中动态多态的实现依赖于虚函数表。在程序编译期间,(大多数)编译器会为有虚函数的类初始化一个虚函数表,它的每一个表项是一个函数指针,指向该类的某一个虚函数。同时,编译器还会为该类添加一个隐藏的成员变量,它是一个指针,叫做虚函数表指针,用来指向该类的虚函数表。在程序运行期间,当实例化该类的一个对象时,会有一部分隐藏的代码用来初始化(或者叫做设置)该对象的虚函数表指针。虚函数表指针的示例代码如下所示:

// Your original C++ source code
class Base {
public:
  // ...
  FunctionPtr* __vptr;  // Supplied by the compiler, hidden from the programmer
  // ...
};

这里的 __vptr就是编译器为 Base类添加的虚函数表指针。

经过查阅资料得知,在实例化一个 C++对象的过程中,它的虚函数表指针(如果有的话)是在构造函数初始化列表里面设置的,示例代码如下所示:

Base::Base( /*...arbitrary params...*/ )
  : __vptr(&Base::__vtable[0])  // Supplied by the compiler, hidden from the programmer
  // ...
{
  // ...
}

这里的 Base::__vtable[]数组就是 Base类的虚函数表。

如果是实例化一个派生类的对象,则是先实例化基类部分的对象,再实例化派生类部分的对象,这和普通类的实例化流程是一样的。但是,这个派生类对象的虚函数表指针会被设置两次,第一次是在初始化基类对象时,第二次是在初始化派生类对象时。也就是说,如果在基类的构造函数中调用虚函数,则是调用的基类的虚函数,如果在派生类的构造函数中调用虚函数,则是调用的派生类的虚函数。

下面这个示例代码演示了在基类和派生类的构造函数中分别调用虚函数的场景:

#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
        printVTable(); // 查看构造过程中的 vptr
    }

    virtual void func1() {
        cout << "Base::func1()" << endl;
    }

    virtual void func2() {
        cout << "Base::func2()" << endl;
    }

    void printVTable() {
        // 通过 this 指针获取 vptr
        void** vptr = *(void***)this;
        cout << "VTable address: " << vptr << endl;
        cout << "func1 address from vtbl: " << vptr[0] << endl;
        cout << "func2 address from vtbl: " << vptr[1] << endl;

        using funcPtr = void (*)(void);
        funcPtr f1 = (funcPtr)vptr[0];
        funcPtr f2 = (funcPtr)vptr[1];

        f1();
        f2();
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
        printVTable(); // 查看构造完成后的 vptr
    }

    void func1() override {
        cout << "Derived::func1()" << endl;
    }

    void func2() override {
        cout << "Derived::func2()" << endl;
    }
};

int main() {
    cout << "Creating Base object:" << endl;
    Base base;

    cout << "\nCreating Derived object:" << endl;
    Derived derived;

    return 0;
}

上述示例代码的运行结果如下所示:

$ g++ -o main main.cpp --std=c++11
$ ./main
Creating Base object:
Base constructor
VTable address: 0x102c200b8
func1 address from vtbl: 0x102c1c848
func2 address from vtbl: 0x102c1c884
Base::func1()
Base::func2()

Creating Derived object:
Base constructor
VTable address: 0x102c200b8
func1 address from vtbl: 0x102c1c848
func2 address from vtbl: 0x102c1c884
Base::func1()
Base::func2()
Derived constructor
VTable address: 0x102c200e8
func1 address from vtbl: 0x102c1c928
func2 address from vtbl: 0x102c1c964
Derived::func1()
Derived::func2()

从上面的输出结果可以看到,在对象的实例化过程中,虚函数表指针的值发生了变化。在基类的构造函数中和在派生类的构造函数中,虚函数表指针的值是不同的,分别指向了基类的虚函数表和派生类的虚函数表。

参考资料:
https://isocpp.org/wiki/faq/virtual-functions#dyn-binding2

更多推荐