逐步解說:在 Visual C++ Microsoft 中建置和匯入標頭單位
本文是關於使用 Visual Studio 2022 建置和匯入標頭單位。 若要瞭解如何將C++標準連結庫標頭匯入為標頭單位,請參閱 逐步解說:將 STL 連結庫匯入為標頭單位。 如需更快速且更可靠的方式來匯入標準程式庫,請參閱教學課程:使用模組匯入 C++ 標準程式庫。
標頭單位是先行編譯頭檔 (PCH) 的建議替代方案。 標頭單位更容易設定及使用、在磁碟上明顯較小、提供類似的效能優點,而且比 共用 PCH 更有彈性。
若要對比標頭單位與其他在程式中包含功能的方式,請參閱 比較標頭單位、模組和先行編譯標頭。
必要條件
若要使用標頭單位,您需要 Visual Studio 2019 16.10 或更新版本。
什麼是標頭單位
標頭單元是標頭檔的二進位表示法。 標頭單位以延伸模組結尾 .ifc
。 相同的格式用於具名模組。
標頭單位和頭文件之間的重要差異在於標頭單位不受標頭單位外部的巨集定義影響。 也就是說,您無法定義預處理器符號,導致標頭單位的行為不同。 當您匯入標頭單位時,標頭單位已經編譯。 這與 #include
處理檔案的方式不同。 內含的檔案可能會受到頭檔外部的巨集定義影響,因為頭檔會在編譯包含該檔案的來源檔案時經過預處理器。
標頭單位可以依任何順序匯入,但頭檔不是 true。 頭文件順序很重要,因為一個頭檔中定義的巨集定義可能會影響後續頭檔。 一個標頭單位中的巨集定義不會影響另一個標頭單位。
頭文件中顯示的所有專案也會從標頭單位顯示,包括標頭單位內定義的巨集。
標頭文件必須轉譯為標頭單元,才能匯入。 標頭單位優於先行編譯頭檔 (PCH) 的優點是,它們可用於分散式組建。 只要您使用相同的編譯程式來編譯 .ifc
和 程式,並以相同的平臺和架構為目標,就可以在另一部計算機上取用一部計算機上產生的標頭單位。 與 PCH 不同的是,當標頭單位變更時,只會變更它,以及重建相依的內容。 標頭單位的大小上限可以小於 的大小 .pch
。
標頭單位會對編譯程序參數組合的必要相似性施加較少的條件約束,這些條件約束用於建立標頭單位,以及編譯取用它的程序代碼,而不是 PCH。 不過,某些切換組合和巨集定義可能會在各種轉譯單位之間建立一個定義規則 (ODR) 的違規。
最後,標頭單位比 PCH 更有彈性。 使用 PCH 時,您無法選擇只引進 PCH 中的其中一個標頭,編譯程式會處理所有標頭。 使用標頭單位,即使您將它們一起編譯成靜態庫,您也只會將匯入至應用程式的標頭單位內容。
標頭單位是頭檔與 C++20 模組之間的步驟。 它們提供模組的一些優點。 它們更強固,因為外部巨集定義不會影響它們,因此您可以依任何順序匯入它們。 編譯程式可以比標頭檔更快處理它們。 但是標頭單位沒有模組的所有優點,因為標頭單位會公開其中定義的巨集(模組不會)。 不同於模組,在標頭單元中無法隱藏私人實作。 若要指出頭檔的私人實作,會採用不同的技術,例如將前置底線新增至名稱,或將專案放入實作命名空間中。 模組不會以任何形式公開私人實作,因此您不需要這麼做。
請考慮將先行編譯的標頭取代為標頭單位。 您獲得相同的速度優勢,但也有其他程式代碼衛生和彈性優點。
編譯標頭單位的方式
有數種方式可將檔案編譯成標頭單元:
建置共用標頭單位專案。 我們建議使用此方法,因為它能更充分地控制組織,並重複使用匯入的標頭單位。 建立靜態庫專案,其中包含您想要的標頭單位,然後參考它以匯入標頭單位。 如需此方法的逐步解說,請參閱 建置標頭單位靜態庫專案的標頭單位靜態庫專案。
選擇要轉譯成標頭單位的個別檔案。 此方法可讓您逐一控制視為標頭單位的內容。 當您必須將檔案編譯為標頭單位時,它也很有用,因為它沒有預設擴展名 (
.ixx
、.cppm
.h
.hpp
), 通常不會編譯成標頭單位。 這個逐步解說會示範這個方法。 若要開始使用,請參閱 方法 1:將特定檔案轉譯為標頭單元。自動掃描和建置標頭單位。 這種方法很方便,但最適合較小的項目,因為它不保證最佳的建置輸送量。 如需此方法的詳細資訊,請參閱 方法 2:自動掃描標頭單位。
如簡介中所述,您可以將 STL 頭檔建置和匯入為標頭單位,並自動將 STL 連結庫標頭
import
視為#include
不重寫程式碼。 若要查看做法,請流覽 逐步解說:將 STL 連結庫匯入為標頭單位。
方法 1:將特定檔案轉譯為標頭單元
本節說明如何選擇要轉譯為標頭單元的特定檔案。 使用 Visual Studio 中的下列步驟,將頭文件編譯為標頭單位:
建立新的C++控制台應用程式專案。
取代來源檔案內容,如下所示:
#include "Pythagorean.h" int main() { PrintPythagoreanTriple(2,3); return 0; }
新增名為
Pythagorean.h
的頭檔,然後使用下列程式代碼取代其內容:#ifndef PYTHAGOREAN #define PYTHAGOREAN #include <iostream> inline void PrintPythagoreanTriple(int a, int b) { std::cout << "Pythagorean triple a:" << a << " b:" << b << " c:" << a*a + b*b << std::endl; } #endif
設定專案屬性
若要啟用標頭單位,請先使用下列步驟, 將 C++ Language Standard 設定為 /std:c++20
或更新版本:
- 在 方案總管 中,以滑鼠右鍵按下專案名稱,然後選擇 [屬性]。
- 在專案屬性頁視窗的左窗格中,選取 [ 組態屬性>一般]。
- 在 [C++語言標準 ] 下拉式清單中,選取 [ISO C++20 Standard (/std:c++20) 或更新版本。 選擇 [ 確定 ] 以關閉對話框。
將頭文件編譯為標頭單位:
在 方案總管 中,選取您要編譯為標頭單元的檔案(在此案例中為
Pythagorean.h
)。 以滑鼠右鍵按下檔案,然後選擇 [ 屬性]。將 [組態屬性>一般>項目類型] 下拉式清單設定為 C/C++編譯程式,然後選擇 [確定]。
當您在本逐步解說稍後建置此專案時, Pythagorean.h
將會轉譯為標頭單元。 因為此頭檔的項目類型設定為 C/C++ 編譯程式,因此會轉譯成標頭單元,因此,因為和 .hpp
檔案的默認動作.h
是將檔案轉譯成標頭單位。
注意
這不是本逐步解說的必要專案,但會針對您的資訊提供。 若要將檔案編譯為沒有預設頭單元擴展名的標頭單位,例如,設定組態屬性>C/C++Advanced>Compile As to Compile as C++> Header Unit (/exportHeader):.cpp
變更程式代碼以匯入標頭單元
在範例專案的來源檔案中,將 變更
#include "Pythagorean.h"
為import "Pythagorean.h";
[別忘了尾端分號]。 語句需要import
它。 因為它是專案本機目錄中的頭檔,所以我們在import
語句中使用引號:import "file";
。 在您自己的專案中,若要從系統標頭編譯標頭單位,請使用角括弧:import <file>;
選取主功能表上的 [建置]>[建置方案] 以建置方案。 執行它以檢視它產生預期的輸出:
Pythagorean triple a:2 b:3 c:13
在您自己的專案中,重複此程式,以編譯您要匯入為標頭單位的頭檔。
如果您只想將少數頭檔轉換成頭檔,這個方法很好。 但是,如果您有許多想要編譯的頭檔,而且建置效能的潛在遺失,超過建置系統自動處理它們的便利性,請參閱下一節。
如果您特別想要將 STL 連結庫標頭匯入為標頭單位,請參閱 逐步解說:將 STL 連結庫匯入為標頭單位。
方法 2:自動掃描和建置標頭單位
由於掃描所有原始程式檔的標頭單位需要時間,以及建置它們的時間,因此下列方法最適合較小的專案。 它不保證最佳的建置輸送量。
此方法結合了兩個 Visual Studio 專案設定:
- 掃描模組相依性 的來源會導致建置系統呼叫編譯程式,以確保所有匯入的模組和標頭單位都是在編譯相依於它們的檔案之前建置的。 結合 Translate Include to Imports 時,來源中包含的任何頭文件,這些頭檔也指定於
header-units.json
與頭檔位於相同目錄中的檔案中,會編譯成標頭單位。 - Translate Includes to Imports 會將頭檔
import
視為 ,如果#include
參考可編譯為頭文件頭檔(如檔案中所header-units.json
指定),而且頭檔可以使用編譯頭檔。 否則,頭檔會被視為一般#include
。 檔案header-units.json
是用來自動建置每個#include
的標頭單位,而不需要重複符號。
您可以在項目的屬性中開啟這些設定。 若要這樣做,請以滑鼠右鍵按兩下 方案總管中的項目,然後選擇 [屬性]。 然後選擇 [組態屬性>C/C++>一般]。
[模組相依性掃描來源] 可以針對專案內容中專案中的所有檔案設定,如這裡所示,或針對 [檔案屬性] 中的個別檔案進行設定。 一律會掃描模組和標頭單位。 當您有一個 .cpp
檔案匯入您想要自動建置且可能尚未建置的標頭單位時,請設定此選項。
這些設定會一起運作,以在這些情況下自動建置和匯入標頭單位:
- [模組相依性 掃描來源] 會掃描您的來源,以取得檔案及其可視為標頭單位的相依性。 不論此設定為何,擴展名為
.ixx
的檔案,以及其 File 屬性> C/C++Compile As 屬性設定為 [編譯為 C++> 頭單元 / 匯出] 的檔案,一律會掃描。 編譯程式也會尋找import
語句來識別標頭單位相依性。 如果/translateInclude
指定 ,編譯程式也會掃描#include
檔案中也指定為標頭單位的header-units.json
指示詞。 相依性圖形是建置專案中所有模組和標頭單位。 - 將 Include 轉譯為匯 入 當編譯程式遇到
#include
語句,且指定的頭檔存在相符的頭單元檔案 (.ifc
) 時,編譯程式會匯入標頭單位,而不是將頭檔#include
視為 。 與 掃描相依性結合時,編譯程式會尋找可編譯成標頭單位的所有頭檔。 編譯程式會諮詢允許清單,以決定哪些標頭檔可以編譯成標頭單位。 此清單會儲存在header-units.json
必須與內含檔案位於相同目錄中的檔案中。 您可以在 Visual Studio 的安裝目錄下看到檔案的header-units.json
範例。 例如,%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json
編譯程式會使用 來判斷是否可以將標準範本連結庫標頭編譯成標頭單位。 這項功能可用來做為舊版程式代碼的網橋,以取得標頭單位的一些優點。
檔案 header-units.json
有兩個用途。 除了指定哪些頭檔可以編譯成標頭單位之外,還可以將重複的符號降到最低,以增加建置輸送量。 如需符號重複的詳細資訊,請參閱 C++header-units.json參考。
這些參數和 header-unit.json
提供標頭單位的一些優點。 為了方便起見,建置輸送量會付出代價。 這種方法可能不是較大型專案的最佳方法,因為它不保證最佳的建置時間。 此外,可能會重複重新處理相同的頭檔,這會增加建置時間。 不過,根據專案,便利性可能值得。
這些功能是針對舊版程式代碼所設計。 針對新的程式代碼,請移至模組,而不是標頭單位或 #include
檔案。 如需使用模組的教學課程,請參閱命名模組教學課程(C++)。
如需如何使用這項技術將 STL 頭文件匯入為標頭單位的範例,請參閱 逐步解說:將 STL 連結庫匯入為標頭單位。
預處理器含意
需要符合預處理器的標準 C99/C++11,才能建立和使用標頭單位。 編譯程式會啟用新的 C99/C++11,在編譯標頭單位時,只要使用任何形式/exportHeader
,就會隱含地新增/Zc:preprocessor
至命令行,以符合預處理器。 嘗試關閉它會導致編譯錯誤。
啟用新的預處理器會影響 variadic 巨集的處理。 如需詳細資訊,請參閱 Variadic 巨集 備註一節。
另請參閱
/translateInclude
/exportHeader
/headerUnit
header-units.json
比較標頭單位、模組和先行編譯標頭檔
C++ 中的模組概觀
教學課程:使用模組匯入 C++ 標準程式庫
逐步解說:將 STL 連結庫匯入為標頭單位