是的,在某些情况下,添加一个虚函数的重新实现会改变虚函数表的布局。如果您从不是第一个基类(多重继承)的基类重新实现虚函数,就会出现这种情况:
// V1
struct A { virtual void f(); };
struct B { virtual void g(); };
struct C : A, B { virtual void h(); }; //does not reimplement f or g;
// V2
struct C : A, B {
virtual void h();
virtual void g(); //added reimplementation of g()
};
这通过添加一个条目来更改 C 的 vtable 的布局g()
(感谢“Gof”首先引起我的注意,作为http://marcmutz.wordpress.com/2010/07/25/bcsc中的评论-gotcha-reimplementing-a-virtual-function/)。
此外,如其他地方所述,如果您的库的用户以静态类型等于动态类型的方式使用您覆盖函数的类,您会遇到问题。新建后可能会出现这种情况:
MyClass * c = new MyClass;
c->myVirtualFunction(); // not actually virtual at runtime
或在堆栈上创建它:
MyClass c;
c.myVirtualFunction(); // not actually virtual at runtime
其原因是一种称为“去虚拟化”的优化。如果编译器能够在编译时证明对象的动态类型是什么,它就不会通过虚函数表发出间接寻址,而是直接调用正确的函数。
现在,如果用户针对旧版本的库进行编译,编译器将插入对最衍生的虚拟方法重新实现的调用。如果在较新版本的库中,您在派生更多的类中覆盖此虚函数,则针对旧库编译的代码仍将调用旧函数,而编译器无法证明的动态类型的新代码或代码对象在编译时,会经过虚函数表。因此,类的给定实例在运行时可能会遇到对其无法拦截的基类函数的调用,这可能会违反类不变量。