通常我们会认为 c++ 程序中,类的虚函数总是严格按照类中的声明顺序排列,但最近我在查看一个虚函数的调用时却发现并不如此。如果虚函数有候选的重载,那它的顺序实际可能和声明的顺序不一样。

比如下面一个简单的类

class VTableOrder {
public:
  virtual int F1(int) { return 1; }
  virtual int F2(int) { return 2; }
  virtual int F3(int) { return 3; }
  virtual int F2(int*) { return 4; }
  void FF(char) {}
  virtual ~VTableOrder() {};
private:
  int a, b, c;
};

int main() {
  VTableOrder* p = new VTableOrder();
  return 0;
}

在类的声明顺序中,F2(int*) 排在第四个,但用 msvc 编译出来后发现实际位置是在第二个。
编译后的虚函数顺序

观察图中的虚函数地址,可以看到虽然顺序变了,但是函数的地址还是按声明的顺序从小到大排列的,F2(int*) 仍然排在第四。

如果把 F2(int) 和 F2(int*) 的位置互换,发现它们在虚表中的顺序也换过来了。看起来只是单纯的后进先出,不像是有特定的排序规则,比如按名称粉碎后的字典序排列等。
编译后的虚函数顺序(交换)

因此看汇编代码时也需要留意,不能想当然地按函数的声明顺序来对比。