不要在公共接口中傳遞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容器!
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成