2014-01-17 41 views
12

在設計C++庫時,我認爲在公共接口中包含標準庫容器(如std::vector)是一種不好的做法(請參閱,例如Implications of using std::vector in a dll exported function)。安全地在C++庫接口中使用容器

如果我想公開一個獲取或返回對象列表的函數,該怎麼辦?我可以使用一個簡單的數組,但是接下來我將不得不添加一個count參數,這會使界面更加繁瑣且不太安全。例如,如果我想使用map,它也不會有多大幫助。我猜像Qt這樣的庫定義了他們自己的容器,這些容器可以安全地導出,但我寧願不添加Qt作爲依賴項,並且我不想滾動自己的容器。

在庫接口中處理容器的最佳做法是什麼?是否有可能使用「膠水」的小容器實現(最好只有一個或兩個文件,我可以使用許可證)?或者,是否有一種方法可以使.DLL/.so界限和不同的編譯器可以安全地使用std::vector等。

回答

3

您可以實現模板功能。這有兩個優點:

  1. 它可以讓你的用戶決定他們想用你的界面什麼種類的容器。
  2. 它讓您不必擔心ABI的兼容性問題,因爲庫中沒有代碼,它會在用戶調用函數時實例化。

例如,把這個在你的頭文件:

template <typename Iterator> 
void foo(Iterator begin, Iterator end) 
{ 
    for (Iterator it = begin; it != end; ++it) 
    bar(*it); // a function in your library, whose ABI doesn't depend on any container 
} 

然後用戶可以調用FOO與任何容器類型,甚至是那些他們發明了,你不知道。

一個缺點是您需要公開實現代碼,至少對於foo來說。

編輯:你也說你可能想要返回一個容器。考慮像一個回調函數的替代品,如黃金時光在C:

typedef bool(*Callback)(int value, void* userData); 
void getElements(Callback cb, void* userData) // implementation in .cpp file, not header 
{ 
    for (int value : internalContainer) 
    if (!cb(value, userData)) 
     break; 
} 

這是一個非常老派的「C」的方式,但它給你一個穩定的界面,是基本上任何來電者非常有用(甚至實際的C代碼進行小的更改)。這兩個怪癖是void * userData,它讓用戶在那裏阻塞一些上下文(比如說他們想調用成員函數)以及bool返回類型讓回調告訴你停止。你可以使用std :: function或其他任何東西使回調變得更加奇特,但這可能會挫敗你的其他一些目標。

+1

我建議「功能模板」,而不是「模板函數」? :) –

4

實際上,這不僅適用於STL容器,而且適用於幾乎任何C++類型(尤其是所有其他標準庫類型)。

由於ABI不標準化,您可能會遇到各種麻煩。通常你必須爲每個支持的編譯器版本提供單獨的二進制文件以使其工作。獲得真正可移植的DLL的唯一方法是堅持一個普通的C接口。這通常會導致類似COM之類的內容,因爲您必須確保所有分配和匹配解除分配發生在同一模塊中,並且實際對象佈局的任何細節都不會暴露給用戶。

+0

也許不是所有的標準庫類型 - 我敢打賭你可以在一堆工具鏈上使用std :: pair,array,bitset和tuple。但它肯定不能保證 - 我只是頭腦風暴stdlib的哪些部分可能沒有毒性。 –

+0

@JohnZwinck:我不會**賭'tuple';如果我記得正確的話,libstdC++和libC++版本差別很大。 –

+0

@ComicSansMS:是的,我只是想過COM,想知道他們是如何解決那裏的問題的......這導致了VARIANT和SAFEARRAYs的兔子洞。我對COM的瞭解越多,我就越意識到這是如此複雜的原因。 – jdm

3

TL; DR如果您爲各種支持的(ABI +標準庫實現)集合分發源代碼或編譯的二進制文件,則不存在任何問題。

通常,後者被看作是繁瑣的(具有原因),因此,指引。


我相信手揮舞着的指導方針,只要我可以扔他們...我鼓勵你也這樣做。

本指南源於ABI兼容性問題:ABI是一組複雜的規範,它定義了已編譯庫的接口確切的接口。它特別包括:

  • 結構的內存佈局
  • 功能的名字改編
  • 函數的調用約定
  • 異常,運行時類型信息的處理,...
  • ...

有關更多詳細信息,請檢查例如Itanium ABI。與具有非常簡單的ABI的C相反,C++具有更復雜的表面區域...因此爲它創建了許多不同的ABI。

除了ABI兼容性之外,標準庫實現也存在一個問題。大多數編譯器都帶有自己的標準庫實現,這些實現與其他實現不兼容(例如,即使所有實現相同的接口和保證,它們都不會以相同的方式表示std::vector)。

結果,已編譯的二進制(可執行文件或庫)只可以混合和匹配與另一個編譯的二進制如果兩者都針對同一ABI,並用標準庫實現的兼容版本編譯。

乾杯:如果你發佈的源代碼,讓客戶沒有編譯問題。

+0

你能評論這個相關的[問答](https://stackoverflow.com/questions/49059675/transitioning-away-from-stdstring-stdostream-etc-in-a-librarys-public-ap)? – metal