2013-02-14 139 views
6

现在,我知道向非叶类添加新的虚函数通常是不好的,因为它会破坏任何派生类的二进制兼容性,而这些类没有被重新编译。但是,我有一个稍微不同的情况:纯虚函数和二进制兼容性

我编译成一个共享库的接口类和实现类,例如:

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     ... 
} 

class Impl { 
    public: 
     ... 
     void Foo(uint16_t arg); 
     .... 
} 

我主要的应用程序使用此共享库,并且基本上可以写成为:

Interface* foo = Implementation::giveMeImplPtr(); 
foo->Foo(0xff); 

换句话说,该应用程序不具有从Interface派生的任何类,它仅仅使用它。

现在,说我想重载Foo(uint16_t arg)Foo(uint32_t arg),我是做安全:

class Interface { 
    public: 
     static Interface* giveMeImplPtr(); 
     ... 
     virtual void Foo(uint16_t arg) = 0; 
     virtual void Foo(uint32_t arg) = 0; 
     ... 
} 

,并重新编译我的共享库,而无需重新编译应用程序?

如果是这样,有什么不寻常的注意事项我需要注意?如果没有,除了使用命中和升级版本库之外,还有其他选择,从而打破向后兼容性吗?

回答

5

ABI基本上取决于对象的大小和形状,包括vtable。添加一个虚函数肯定会改变vtable,它的改变取决于编译器。

在这种情况下需要考虑的其他事情是,您不仅提出了ABI重大更改,而且还提出了一个在编译时很难检测到的API。如果这些人不是虚函数和ABI兼容性不是一个问题,你的改变之后,是这样的:

void f(Interface * i) { 
    i->Foo(1) 
} 

会悄悄最终调用您的新功能,但前提是该代码重新编译,它可以使调试非常困难。

5

简单的答案是:不。无论何时您更改 定义的类别全部,您都有可能失去二进制兼容性。 在实践中添加非虚函数或静态成员通常是安全的 ,虽然仍然是正式的未定义行为,但 就是这样。其他任何可能会破坏二进制 兼容性。

2

这是非常惊人的对我来说,当我在类似的情况,我发现,MSVC 逆转重载函数的顺序。根据你的榜样,MSVC将建设v_table(二进制)是这样的:

virtual void Foo(uint32_t arg) = 0; 
virtual void Foo(uint16_t arg) = 0; 

如果我们将扩大一点点你的榜样,像这样:

class Interface { 
    virtual void first() = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void final() = 0; 
} 

MSVC将构建以下v_table :

virtual void first() = 0; 
    virtual void Foo(std::string arg) = 0; 
    virtual void Foo(uint32_t arg) = 0; 
    virtual void Foo(uint16_t arg) = 0; 
    virtual void final() = 0; 

Borland的建设者和GCC不改变顺序,但

  1. 他们不这样在版本,我测试
  2. 如果库由GCC(例如)编译,应用程序将通过MSVC编译,这将是一个史诗般的失败

的结束...永远不要依赖二进制兼容性。任何类的改变都必须重新编译所有的代码,使用它。

2

您试图描述流行“使类非衍生”技术为维护二进制兼容性所使用,例如,在的Symbian C++的API(寻找的NewL工厂方法):

  1. 提供工厂功能;
  2. 声明C++构造私人(和非出口非内联,类不应该有朋友类或函数),这使得类非衍生,然后你可以:

    • 添加虚拟在类声明结尾的函数,
    • 添加数据成员并更改类的大小。

这种方法只能用于GCC编译器,因为它节省的虚函数在二进制级别源顺序。

说明

虚拟功能由在对象的v表的偏移,而不是由错位名调用。如果只能通过调用静态工厂方法来获取对象指针,并保留所有虚函数的偏移量(通过保存源顺序,在最后添加新方法),那么这将是后向二进制兼容的。

,如果你的类有一个公共构造(在线或非在线)的兼容性将被打破:

  • 直列:应用程序将复制类的旧v表和旧的内存布局这与新图书馆使用的不同;如果您调用任何导出的方法或将对象作为参数传递给此方法,那么这可能会导致分段错误的内存损坏;

  • 非内联:情况比较好,因为你可以通过添加新的虚拟方法,以叶类声明的末尾改变v表,因为链接将在搬迁派生类的V表布局客户端,如果您将加载新的库版本;但是仍然无法更改类的大小(即添加新字段),因为大小在编译时被硬编码,调用新版本的构造函数可能会破坏客户端堆栈或堆上相邻对象的内存。

工具

尝试使用abi-compliance-checker工具来检查Linux上的类库版本的向后二进制兼容性。