我的库有两个类,一个基类和一个派生类。在库的当前版本中,基类具有虚函数foo(),派生类不会覆盖它。在下一个版本中,我希望派生类可以覆盖它。这会破坏ABI吗?我知道引入一个新的虚函数通常会,但这似乎是一个特例。我的直觉是它应该改变vtbl中的偏移量,而不是实际改变表的大小。
显然,由于C ++标准没有强制要求特定的ABI,这个问题在某种程度上是特定于平台的,但实际上,在大多数编译器中,断开和维护ABI的方式是相似的。我对GCC的行为感兴趣,但是人们可以回答的编译器越多,这个问题就越有用;)
答案 0 :(得分:9)
可能。
你对偏移有误。 vtable中的偏移量已经确定。会发生什么是Derived类构造函数将使用Derived覆盖替换该偏移处的函数指针(通过将类内v-pointer切换到新的v-table)。所以它通常与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
。
答案 1 :(得分:7)
这看起来似乎并不是特别依赖的东西 - 正如你所说的C ++ ABI非常棘手(甚至是编译器选项)。
那就是说我认为您可以在进行更改之前和之后使用g++ -fdump-class-hierarchy
来查看父级或子级vtable的结构是否发生变化。如果他们不这样做,那么假设你没有打破ABI可能是“相当”安全的。
答案 2 :(得分:3)
是的,在某些情况下,添加虚拟功能的重新实现将更改虚拟功能表的布局。如果您从不是第一个基类(多继承)的基础重新实现虚函数,就是这种情况:
// 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的vtable的布局(感谢“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
这样做的原因是称为“去虚拟化”的优化。如果编译器可以在编译时证明对象的动态类型是什么,它将不会通过虚函数表发出间接,而是直接调用正确的函数。
现在,如果用户针对旧版本的库进行编译,则编译器将插入对最常导出的虚拟方法重新实现的调用。如果在库的较新版本中,您在更多派生类中重写此虚函数,则针对旧库编译的代码仍将调用旧函数,而新编译器无法证明动态类型的代码或代码编译时的对象,将通过虚函数表。因此,在运行时,类的给定实例可能会遇到无法拦截的基类函数调用,可能会导致违反类不变量。
答案 3 :(得分:1)
我的直觉是它应该改变vtbl中的偏移,而不是实际改变表的大小。
嗯,你的直觉显然是错误的:
哪一个是真的取决于很多因素。
无论如何:不要指望它。
答案 4 :(得分:0)
警告:有关此逻辑不成立的情况,请参阅In C++, does overriding an existing virtual function break ABI?;
在我的脑海中,马克建议使用g ++ -fdump-class-hierarchy将成为胜利者,在进行适当的回归测试之后
覆盖的东西不应该改变 vtable layout [1]。 vtable条目本身将在库的数据库中,恕我直言,因此对 it 的更改不应构成问题。
当然,应用程序需要重新链接,否则如果消费者使用直接引用& Derived :: overriddenMethod,则存在潜在的破坏; 我不确定是否允许编译器将其解析为& Base :: overriddenMethod,但是比抱歉更安全。
[1]拼写出来:这假设方法是虚拟的开始!