不要在公共接口中傳遞STL容器

最近的一個項目,是開發一個framework,提供給公司內部不同的產品線使用。 之間遇到的一個問題,就是STL容器的使用, 而結論是不要在公共接口中傳遞STL容器:


這裏說的STL容器,但主要則是指容器,字符串類,但其實可以推廣到在STL中提供的任何類型,
這裏說的公共接口,是指需要暴露給客戶的sdk頭文件,包括函數簽名,或者類成員變量;
也可以說,不要在暴露給客戶的頭文件中包含STL的頭文件。


原因分析
爲什麼有這個結論,我們可以從幾個方面來論述:


客戶端使用的STL版本可能不同
因爲STL作爲標準庫,Framework編譯的時候使用的STL,與客戶端編譯的時候使用的STL,版本是有可能不一樣的,比如Framework使用VC8編譯發佈,而客戶代碼使用VC10編譯使用,那麼Framework所理解的STL容器,與客戶端代碼所理解的STL容器,在內存佈局上,數據表示上是有可能不一樣的,出錯也就不可避免了。
編譯選項
即使Framework和客戶端使用的是同一個版本的STL,但是如果編譯選項不一樣,也可能導致其不一致性,自然也會出錯。
靜態變量
即使版本一致,編譯選項一致,仍然是不安全的,因爲STL容器的實現中,有可能使用了靜態變量,而在Framework模塊和客戶端模塊中,雖然由於模板擴展產生的兩份代碼總體上是一致的,但都各自維護了一份靜態變量,從而造成了內部狀態的不一致性。
客戶端的STL被自定義
但從文件的角度來看,在客戶端包含的STL頭文件實現,可以是完全不同於標準的 - 只要其接口和標準保持一致。 比如客戶那邊使用的STL在內存管理方面有特殊需求,改寫了默認的allocator,那麼內存必然會出問題;比如客戶在實現中加入了其他成員變量,那麼內存佈局必然也不一致。
雖然,微軟這篇文章提到導出vector是安全的,但是根據上面的分析,這應該是不安全的做法。


可選方案
但事實上,你還是可能存在這種需求的 - 你需要容器類的函數參數或者返回值,你可能也需要容器類的類成員變量,那麼如何解決? 


對於前者,我們可以針對具體的類型封裝一個具體的容器類,內部還是可以使用STL container的;
對於後者,我們可以用一些辦法,防止STL容器出現在頭文件中; 


解決的方法,可以有以下幾種:


提供自己的容器類
這是最徹底,最直接的方法,但是提供一套與STL相當的模板容器類需要蠻大的工作量,而且明顯是重複製造輪子了。 要注意,這裏簡單的對STL容器進行封裝是行不通,因爲模板不支持分離編譯,這麼做還是會把STL容器帶入公共接口。
PImpl
PImpl模式當然很強大,當然可以解決這個問題,但是此處如果僅僅是爲了隱藏容器類,難免殺雞用牛刀,還引入了不必要的間接層與繁瑣的腳手架代碼。
使用void*作爲數據成員,而後強制轉換爲容器類
不安全也太醜陋,用起來也繁瑣,每次都要cast以下,除非別無他法,是在不應該考慮這個方案。 
前置聲明STL容器 
這個方法在VC9中是可以工作的,但是根據C++標準: 17.4.3.1 Reserved names,這種做法是未定義的:
“ It is undefined for a C++ program to add declarations or definitions to namespace std or namespaces within namespace std unless otherwise specified”


自定義結構體封裝容器並前置聲明
這個方案是這樣的:
//頭文件
struct PublicClassData;
class PublicClass
{
public:
    PublicClass();


private:
    PublicClassData* m_pData;


}


//實現文件
struct PublicClassData
{
    vector<int> _array;
    map<string, int> _map;
};


PublicClass::PublicClass()
{
    m_pData = new PublicClassData;
}


當然,需要注意的是我們需要遵循:rule of three,提供析構,賦值與拷貝構造函數以管理內存。
這個方案相對來講比較簡潔,應該說是現有方案的最有實踐性的一個。
結論
所以,結論就是不要在DLL/SO的公共接口中使用STL容器,如果你確實需要,那麼請用自定義結構體封裝容器並前置聲明的方式隔離STL容器!




作者: lzprgmr 發表於 2011-07-10 19:32 原文鏈接
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章