C++ 虚函数通常使用虚函数表实现。

多态的原理

通常,编译器处理虚函数的原理是:给每个类实例添加一个隐藏成员,这个隐藏成员保存了一个指向函数指针数组的指针,该数组称为虚函数表,我们用 vtable 指代它,我们用 vptr 指代指向该数组的指针。 而 vptr 位于类实例内存的最前面的位置,由于 vtable 为存储函数指针的数组,故我们可以通过指针操作获得 vtable 的元素,并调用。

代码示例

#include <iostream>

// 定义类时,虚函数的声明顺序,决定了 vtable 内函数指针的顺序

class Base {
 public:
  Base() = default;
  virtual void fun1() { std::cout << "Base::fun1()" << std::endl; }
  virtual void fun2() { std::cout << "Base::fun2()" << std::endl; }
  virtual ~Base() = default;
};

class Derived : public Base {
 public:
  Derived() = default;
  virtual void fun1() { std::cout << "Derived::fun1()" << std::endl; }
  virtual void fun2() { std::cout << "Derived::fun2()" << std::endl; }
  virtual ~Derived() = default;
};

typedef void (*fun)();  // 函数指针

int main() {
  Base* b = nullptr;
  size_t* vptr = nullptr;    // vptr 位于类实例内存的头部
  size_t* vtable = nullptr;  // vtable 即为函数指针数组
  fun pfun = nullptr;

  b = new Base();
  vptr = (size_t*)b;        // 获取 vptr 地址
  vtable = (size_t*)*vptr;  // 获取 vtable 地址

  pfun = (fun)*vtable;  // 获取指针数组的第一元素
  pfun();

  pfun = (fun) * (vtable + 1);  // 获取指针数组的第二元素
  pfun();

  delete b;

  b = new Derived();
  vptr = (size_t*)b;
  vtable = (size_t*)*vptr;

  pfun = (fun)*vtable;
  pfun();

  pfun = (fun) * (vtable + 1);
  pfun();

  delete b;

  return 0;
}