显然,由于C++标准没有强制要求特定的ABI,因此这个问题在某种程度上与平台有关,但实践中什么会破坏和维护ABI在大多数编译器中都是相似的。我对GCC的行为很感兴趣,但是如果更多的编译器可以回答这个问题,那就更有用了;)
可能会有影响。
关于偏移量,你的观点是错误的。在vtable中的偏移量已经被确定了。会发生的事情是,派生类构造函数将使用新的v-table将该偏移处的函数指针替换为派生类的覆盖函数(通过切换类内的v-pointer)。因此,通常来说,它是ABI兼容的。
但是,由于优化,特别是函数调用的去虚拟化,可能会出现问题。
通常情况下,当你调用一个虚函数时,编译器会通过vpointer在vtable中进行查找。然而,如果它能够静态地推断出对象的确切类型,它也可以推断出确切的函数调用并删减掉虚拟的查找过程。
例如:
struct Base {
virtual void foo();
virtual void bar();
};
struct Derived: Base {
virtual void foo();
};
int main(int argc, char* argv[]) {
Derived d;
d.foo(); // It is necessarily Derived::foo
d.bar(); // It is necessarily Base::bar
}
在这种情况下,仅仅链接你的新库并不会捕获Derived::bar
。
总的来说,这似乎不是一个普遍可靠的事情 - 就像你所说的C++ ABI非常棘手(甚至包括编译器选项)。
话虽如此,我认为在进行更改之前和之后可以使用g++ -fdump-class-hierarchy
来查看父类或子类vtable的结构是否发生变化。如果它们没有变化,那么可以“相对”安全地假设您没有破坏ABI。
是的,在某些情况下,添加虚函数的重新实现 将 更改虚函数表的布局。如果您正在从不是第一个基类(多重继承)的基类重新实现虚函数,则是这种情况:
// 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()
};
g()
添加一个条目来更改 C 的虚函数表的布局(感谢“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
注意:请参考在C++中,重写现有的虚函数是否会破坏ABI?这个案例,了解这个逻辑不成立的情况;
在我看来,马克的建议使用g++ -fdump-class-hierarchy将是赢家,在进行适当的回归测试之后。
覆盖应该不会改变 vtable 布局[1]。vtable 条目本身应该在库的 数据段中,所以对 它 的更改不应该造成问题。
当然,应用程序需要重新链接,否则如果消费者一直在使用对 &Derived::overriddenMethod 的直接引用,则存在 潜在的 破坏风险;我不确定编译器是否允许将其解析为 &Base::overriddenMethod,但最好还是小心为妙。
[1] 解释一下:这假定该方法一开始就是虚拟的!