C++20 引進 模組。 模組是一組原始程式碼檔案,這些檔案是獨立於匯入它們的原始檔案(或更準確地說,翻譯單元)編譯的。
模組可消除或減少與標頭檔使用相關的許多問題。 它們通常會大幅減少編譯時間。 模組中宣告的巨集、預處理器指示詞和非匯出名稱不會顯示在模組外部。 它們不會影響匯入模組之翻譯單元的編譯。 您可以依任何順序匯入模組,而不需考慮巨集重新定義。 匯入轉譯單位中的宣告不會參與匯入模組中的多載解析或名稱查閱。 編譯模組一次之後,結果會儲存在二進位檔中,描述所有導出的類型、函式和範本。 編譯程式可以比標頭檔更快處理該檔案。 而且,編譯程式可以在專案中匯入模組的每個位置重複使用它。
您可以與標頭檔案一起使用模組。 C++來源檔案可以包含import模組和#include頭檔。 在某些情況下,您可以將頭文件匯入為模組,這比使用 #include 前置處理器來處理它更快。 我們建議您在新的專案中儘可能使用模組,而非使用頭檔。 針對積極開發的較大型現有項目,嘗試將舊版標頭轉換為模組。 根據是否能顯著縮短編譯時間來決定是否採用。
若要將模組與其他匯入標準連結庫的方式進行對比,請參閱 比較標頭單位、模組和先行編譯標頭。
從 Visual Studio 2022 17.5 版開始,在 Microsoft C++ 編譯程式中,將標準連結庫匯入為模組已標準化且完全實作。 若要瞭解如何使用模組匯入標準連結庫,請參閱 使用模組匯入C++標準連結庫。
單一分區模組
單一分割區模組是由單一原始程式檔所組成的模組。 模組介面和實作位於相同的檔案中。
下列單一分割區模組範例顯示原始程式檔中稱為 Example.ixx的簡單模組定義。 此 .ixx 延伸模組是 Visual Studio 中模組介面檔案的預設擴充功能。 如果您想要使用不同的擴充功能,請使用 /interface 參數將其編譯為模組介面。 在此範例中,介面檔案同時包含函式定義和宣告。 您也可以將定義放在一或多個個別的模組實作檔案中,如稍後範例所示,但這是單一分割區模組的範例。
export module Example;語句指出這個檔案是稱為 Example之模組的主要介面。
export
int f() 修飾詞表示當另一個程式或模組匯入 Example 時,這個函式是可見的。
// Example.ixx
export module Example;
#define ANSWER 42
namespace Example_NS
{
int f_internal()
{
return ANSWER;
}
export int f()
{
return f_internal();
}
}
檔案 MyProgram.cpp 使用 import 來存取由 Example 導出的名稱。 命名空間名稱 Example_NS 在這裡可見,但不是其所有成員,因為它們不會匯出。 此外, ANSWER 巨集不會顯示,因為巨集不會匯出。
// MyProgram.cpp
import std;
import Example;
using namespace std;
int main()
{
cout << "The result of f() is " << Example_NS::f() << endl; // 42
// int i = Example_NS::f_internal(); // C2039
// int j = ANSWER; //C2065
}
宣告 import 只能出現在全域範圍。 模組和取用它的程式代碼必須使用相同的編譯程式選項進行編譯。
模組文法
module-name:
module-name-qualifier-seq選擇identifier
module-name-qualifier-seq:
identifier.
module-name-qualifier-seqidentifier.
module-partition:
:module-name
module-declaration:
export選擇modulemodule-namemodule-partition選擇attribute-specifier-seq選擇;
module-import-declaration:
export選擇importmodule-nameattribute-specifier-seq選擇;
export選擇importmodule-partitionattribute-specifier-seq選擇;
export選擇importheader-nameattribute-specifier-seq選擇;
實作模組
模組介面會匯出模組名稱和構成模組公用介面的所有命名空間、類型、函式等等。
模組實作會定義模組所匯出的項目。
以最簡單的形式,模組可以是結合模組介面和實作的單一檔案。 您也可以將實作放在一或多個個別的模組實作檔案中,類似於 .h 和 .cpp 檔案的實作方式。
針對較大的模組,您可以將模組的元件分割成稱為分割 區的子模組。 每個分割區都包含匯出模組數據分割名稱的模組介面檔案。 分割區也可能有一或多個分割區實作檔案。 整個模組都有一個主要 模組介面,這是模組的公用介面。 如有需要,它可以匯出分割區介面。
模組是由一或多個 模組單位所組成。 模組單元是包含模組宣告的翻譯單位(原始程序檔)。 模組單元有數種類型:
-
模組介面單元會匯出模組名稱或模組分割區名稱。 模組介面單元在其模組宣告中具有
export module。 - 模組實作單元不會匯出模組名稱或模組分割區名稱。 顧名思義,它會實作模組。
- 主要模組介面單元會匯出模組名稱。 模組中必須有一個主要模組介面單元。
- 模組數據分割介面單位會匯出模組數據分割名稱。
-
模組分割實作單位在其模組宣告中具有模組數據分割名稱,但沒有
export關鍵詞。
關鍵詞 export 只用於介面檔案中。 實作檔案可以 import 另一個模組,但它不能 export 有任何名稱。 實作檔案可以有任何擴展名。
模組、命名空間和引數相依查找
模組中命名空間的規則與任何其他程式碼相同。 如果導出命名空間內的宣告,則所屬命名空間(不包括未在該命名空間中明確導出的成員)也會隱含導出。 如果明確匯出命名空間,則會匯出該命名空間定義中的所有宣告。
當編譯器在匯入的翻譯單位中執行參數相依查找以進行多載解析時,它會考慮在同一翻譯單位(包括模組介面)中宣告的函式,作為定義函式參數類型的考量因素。
模組分區
模組分割區與模組類似,不同之處在於:
- 它會共用整個模組中所有宣告的擁有權。
- 分割區介面檔案匯出的所有名稱都會由主要介面檔案匯入和匯出。
- 分割區的名稱必須以模組名稱開頭,後面接著冒號 (
:)。 - 任何分割區中的宣告會在整個模組內顯示。
- 不需要採取任何特殊預防措施,以避免單一定義規則 (ODR) 錯誤。 您可以在一個分割區中宣告名稱(函式、類別等等),並在另一個分割區中定義它。
分割區實作檔案會像這樣開始,而且是C++標準觀點中的內部分割區:
module Example:part1;
分割區介面檔案的開頭如下:
export module Example:part1;
若要存取另一個分割區中的宣告,分割區必須匯入它。 但它只能使用分割區名稱,而不是模組名稱:
module Example:part2;
import :part1;
主要介面單位必須匯入和重新匯出模組的所有介面分割區檔案,如下所示:
export import :part1;
export import :part2;
主要介面單位可以匯入分割區實作檔案,但無法匯出它們。 不允許這些檔案匯出任何名稱。 這項限制可讓模組保持模組內部的實作詳細數據。
模組和標頭檔
您可以在模組宣告之前放置 #include 指令,以在模組的源檔案中包含標頭檔。 這些檔案會被視為在 全域模組片段中。 模組只能查看它明確包含的標頭中全域模組片段的名稱。 全域模組片段只包含所使用的符號。
// MyModuleA.cpp
#include "customlib.h"
#include "anotherlib.h"
import std;
import MyModuleB;
//... rest of file
您可以使用傳統標頭檔來控制匯入的模組:
// MyProgram.h
#ifdef C_RUNTIME_GLOBALS
import std.compat;
#else
import std;
#endif
匯入的頭文件
有些標籤已足夠獨立,因此可以使用 import 關鍵字導入。 匯入標頭和匯入模組之間的主要差異在於,標頭中的任何預處理器定義都會在語句之後 import 的匯入程式中立即顯示。
import <vector>;
import "myheader.h";