C++二進位制相容問題及解決方法
二級制相容
二進位制相容ABI(application binary interface)主要指動態庫檔案單獨升級,現有用到老動態庫的應用程式是否受到影響。
二進位制相容:
1 升級庫檔案,不影響使用庫檔案的程式。
2 新庫必然有新標頭檔案,但是舊的二進位制可執行檔案還是按照舊的標頭檔案中的“使用說明”來呼叫庫。
UseSharedLibrary.exe SharedLibrary.dll
二進位制不相容示例
1 類的普通成員函式 void f( int ) 改成了 void f( double ) 。老EXE會傳int進來,新庫會用double的長度取資料。從而發生undefined symbol
2 基類增加虛擬函式會導致基類虛表發生變化。老EXE呼叫虛表的時候給出的slot是老的,但是新庫裡面的這個slot已經是另一個函數了。
3 給函式增加預設引數
4 增加預設模板型別
5 改變enum的值
6 給class Bar增加資料成員導致sizeof(Bar)的值變大
7 如果EXE裡呼叫new Bar,導致new出來的記憶體盛不下新的Bar物件(建構函式會使用新DLL中的建構函式來填充資料),從而:
1)如果新的庫實現訪問了新的資料成員肯定會訪問到一個無法預知的地方;
2)如果EXE得到的是shared_ptr<Bar> 由DLL來管理記憶體,那麼此時是安全的。
3)如果EXE呼叫的是p->member 那麼肯定不對,因為偏移量可能因為member前面插入了新的成員而被新DLL中建構函式填充了新的成員,從而訪問的並不是老的member。
4)如果EXE是使用p->get_member()來獲取資料,那麼是正常的。
5) 如果p->get_member()是inline的,那麼是不安全的,因為偏移量已經在EXE中了。
8 虛擬函式做介面的基本上都是二進位制不相容的。
二進位制安全的場景:
1 增加新的class(定義在新DLL中,老的EXE裡沒有)
2 增加非virtual函式(定義在新DLL中,老的EXE裡沒有)
3 增加static成員函式(定義在新DLL中,老的EXE裡沒有)
解決辦法之pimpl技法:
1 標頭檔案只暴露非virtual函式,class的大小固定為sizeof(Impl*)
//標頭檔案
class Graphics
{
public:
Graphics();
~Graphics();//不能是虛擬函式
void f1(void);
void f2(int);
void f3(int, int);//增加成員函式可以直接新增非virtual函式,不影響二進位制相容
private:
class Impl;//標頭檔案只放宣告,sizeof(Graphics)不會變化,成員變數的擴充在Impl中新增
boost::scoped_ptr<Impl> m_impl;//sizeof(Graphics) == sizeof(Graphics::Impl*)
}
//庫的原始檔
Graphics::Graphics()
:m_imp(new Impl)
{
}
Graphics::~Graphics()
{
}
void Graphics::f1(void)
{
m_impl->f1();//呼叫轉發
}
繼承和虛擬函式是萬惡之源
1 繼承體系一旦形成,調整起來就開始費勁。
2 Go語言沒有繼承
C++介面的終極方案
設計模式(四)std::function介面程式設計徹底取代抽象工廠和工廠方法