2013-02-14 59 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上的類庫版本的向後二進制兼容性。