簡體   English   中英

僅頭文件庫的好處

[英]Benefits of header-only libraries

僅標頭庫的好處是什么,為什么要以這種方式編寫它而不是將實現放入單獨的文件中?

僅標頭庫的好處:

  • 簡化構建過程。 您不需要構建庫,也不需要在構建的鏈接步驟中指定編譯的庫。 如果你有一個編譯庫,你可能想要構建它的多個版本:一個編譯啟用調試,另一個啟用優化,可能還有另一個剝離符號。 對於多平台系統,甚至更多。

僅標頭庫的缺點:

  • 更大的目標文件。 在某個源文件中使用的庫中的每個內聯方法也將獲得一個弱符號,即該源文件的編譯目標文件中的外聯定義。 這會減慢編譯器的速度,也會減慢鏈接器的速度。 編譯器必須生成所有這些膨脹,然后鏈接器必須過濾掉它。

  • 更長的編譯。 除了上面提到的膨脹問題之外,編譯將花費更長的時間,因為與編譯庫相比,僅頭文件庫的頭文件本質上更大。 需要為使用該庫的每個源文件解析這些大頭文件。 另一個因素是,僅頭文件庫中的頭文件必須#include內聯定義所需的頭文件,以及將庫構建為編譯庫時所需的頭文件。

  • 比較糾結的編譯。 由於只包含頭文件的庫需要額外的#include因此使用只包含頭文件的庫會獲得更多依賴項。 更改庫中某些關鍵函數的實現,您可能需要重新編譯整個項目。 在已編譯庫的源文件中進行更改,您所要做的就是重新編譯該庫源文件,使用新的 .o 文件更新已編譯的庫,然后重新鏈接應用程序。

  • 人類更難閱讀。 即使有最好的文檔,庫的用戶也經常不得不閱讀庫的標題。 僅標頭庫中的標頭充滿了妨礙理解接口的實現細節。 使用編譯后的庫,您所看到的只是接口和對實現功能的簡要說明,這通常就是您想要的。 這才是你真正想要的。 您不必了解實現細節即可了解如何使用該庫。

在某些情況下,只有頭文件庫是唯一的選擇,例如在處理模板時。

擁有僅頭文件的庫還意味着您不必擔心可能使用該庫的不同平台。 分離實現時,通常這樣做是為了隱藏實現細節,並將庫作為標頭和庫( libdll.so文件)的組合進行分發。 這些當然必須針對您提供支持的所有不同操作系統/版本進行編譯。

您也可以分發實現文件,但這對用戶來說意味着一個額外的步驟 - 在使用它之前編譯您的庫。

當然,這適用於具體情況 例如,僅標頭的庫有時會增加代碼大小 & 編譯時間。

我知道這是一個舊線程,但沒有人提到 ABI 接口或特定的編譯器問題。 所以我想我會的。

這基本上基於這樣一個概念:要么編寫一個帶有標題的庫以分發給人們,要么重用自己而不是將所有內容都放在標題中。 如果您正在考慮重用頭文件和源文件並在每個項目中重新編譯它們,那么這實際上並不適用。

基本上,如果您編譯 C++ 代碼並使用一個編譯器構建一個庫,那么用戶嘗試使用不同的編譯器或同一編譯器的不同版本使用該庫,那么由於二進制不兼容,您可能會遇到鏈接器錯誤或奇怪的運行時行為。

例如,編譯器供應商經常在版本之間更改他們的 STL 實現。 如果庫中有一個接受 std::vector 的函數,那么它期望該類中的字節按照編譯庫時的排列方式進行排列。 如果在新的編譯器版本中,供應商對 std::vector 進行了效率改進,那么用戶的代碼會看到可能具有不同結構的新類並將該新結構傳遞到您的庫中。 一切都從那里開始走下坡路......這就是為什么建議不要跨庫邊界傳遞 STL 對象。 這同樣適用於 C 運行時 (CRT) 類型。

在談論 CRT 時,您的庫和用戶的源代碼通常需要針對同一個 CRT 進行鏈接。 使用 Visual Studio,如果您使用多線程 CRT 構建庫,但用戶針對多線程調試 CRT 進行鏈接,那么您將遇到鏈接問題,因為您的庫可能找不到所需的符號。 我不記得它是哪個函數,但是對於 Visual Studio 2015,Microsoft 內聯了一個 CRT 函數。 突然,它在標頭中而不是 CRT 庫中,因此期望在鏈接時找到它的庫不再能做,這會產生鏈接錯誤。 結果是這些庫需要使用 Visual Studio 2015 重新編譯。

如果您使用 Windows API,但您使用不同的 Unicode 設置構建庫用戶,您也可能會收到鏈接錯誤或奇怪的行為。 這是因為 Windows API 具有使用 Unicode 或 ASCII 字符串和宏/定義的函數,它們根據項目的 Unicode 設置自動使用正確的類型。 如果您跨庫邊界傳遞錯誤類型的字符串,那么事情會在運行時中斷。 或者您可能會發現該程序一開始就沒有鏈接。

這些事情對於從其他第三方庫(例如特征向量或 GSL 矩陣)跨庫邊界傳遞對象/類型也是正確的。 如果第 3 方庫在您編譯您的庫和您的用戶編譯他們的代碼之間更改了它們的標頭,那么事情就會中斷。

基本上,為了安全起見,您可以跨庫邊界傳遞的唯一內容是內置類型和普通舊數據 (POD)。 理想情況下,任何 POD 都應位於您自己的標頭中定義的結構中,並且不依賴於任何第三方標頭。

如果您提供僅標頭庫,那么所有代碼​​都將使用相同的編譯器設置並針對相同的標頭進行編譯,因此很多這些問題都會消失(前提是您和您的用戶使用的第三方庫的版本與 API 兼容)。

然而,上面已經提到了一些負面因素,例如編譯時間增加。 此外,您可能正在經營一家企業,因此您可能不想將所有源代碼實現細節交給所有用戶,以防他們中的一個人竊取它。

主要的“好處”是它要求您提供源代碼,因此您最終會在機器上和您從未聽說過的編譯器上得到錯誤報告。 當庫完全是模板時,您沒有太多選擇,但是當您有選擇時,僅標頭通常是一個糟糕的工程選擇。 (當然,另一方面,標題僅意味着您不必記錄任何集成過程。)

內聯可以通過鏈接時間優化 (LTO) 完成

我想強調這一點,因為它降低了僅標頭庫的兩個主要優勢之一的價值:“您需要對標頭進行定義以進行內聯”。

一個最小的具體例子顯示在: 鏈接時優化和內聯

所以你只需要傳遞一個標志,內聯就可以跨對象文件完成,無需任何重構工作,不再需要在頭文件中保留定義。

然而,LTO 也可能有其自身的缺點: 是否有理由不使用鏈接時優化 (LTO)?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM