C++ 中的模組概觀
C++20 引進了模組。 模組是一組原始程式碼檔案,可獨立於來源檔案進行編譯 (或更精確地說是匯入來源檔案的編譯單位)。
模組可消除或減少與標頭檔使用相關聯的許多問題。 它們通常可減少 (有時可大幅減少) 編譯時間。 模組中宣告的巨集、前置處理器指示詞和非匯出的名稱不會顯示在模組外部。 它們不會影響匯入模組之編譯單位的編譯。 您可以依任何順序匯入模組,而不需考慮巨集重新定義。 匯入編譯單位中的宣告不會參與匯入模組中的多載解析或名稱查閱。 編譯模組一次之後,結果會儲存在二進位檔中以描述所有匯出的類型、函式和範本。 編譯器可以比標頭檔更快處理該檔案。 而且,編譯器可以在專案中匯入模組的每個位置重複使用它。
您可以與標頭檔並存使用模組。 C++ 來源檔案可以 import
模組和 #include
標頭檔。 在某些情況下,您可以將頭文件匯入為模組,這比使用 #include
搭配前置處理器來處理它更快。 建議您在新的專案中使用模組,而不是盡可能使用更多的標頭檔。 針對主動式開發中較大型的現有專案,實驗將舊版標頭轉換成模組。 根據您是否獲得有意義的編譯時間縮減來決定是否採用。
若要將模組與其他匯入標準程式庫的方式進行對比,請參閱比較標頭單位、模組和先行編譯標頭檔。
在 Microsoft C++ 編譯器中啟用模組
自 Visual Studio 2022 17.1 版起,C++20 標準模組會在 Microsoft C++ 編譯器中完全實作。
在 C++20 標準指定之前,Microsoft 對於模組具有實驗性支援。 編譯器也支援匯入預先建置的標準程式庫模組,如下所述。
從 Visual Studio 2022 17.5 版開始,將標準程式庫匯入為模組會在 Microsoft C++ 編譯器中標準化且完整實作。 本節說明仍支援較舊的實驗方法。 如需使用模組匯入標準程式庫之新標準化方式的相關資訊,請參閱使用模組匯入 C++ 標準程式庫。
您可以使用模組功能來建立單一分割區模組,以及匯入 Microsoft 所提供的標準程式庫模組。 若要啟用標準程式庫模組的支援,請使用 /experimental:module
和 /std:c++latest
進行編譯。 在 Visual Studio 專案中,以滑鼠右鍵按一下 [方案總管] 中的專案節點,然後選擇 [屬性]。 將 [組態] 下拉式清單設定為 [所有組態],然後選擇 [設定屬性]>[C/C++]>[語言]>[啟用 C++ 模組 (實驗性)]。
模組和取用它的程式碼必須使用相同的編譯器選項進行編譯。
使用 C++ 標準程式庫作為模組 (實驗性)
本節說明仍受支援的實驗性實作。 使用 C++ 標準程式庫作為模組的新標準化方式,如使用模組匯入 C++ 標準程式庫中所述。
藉由將 C++ 標準程式庫匯入為模組,而不是透過標頭檔將其納入,您可以根據專案的大小加快編譯時間。 實驗性程式庫會分割成下列具名模組:
std.regex
提供標頭<regex>
的內容std.filesystem
提供標頭<filesystem>
的內容std.memory
提供標頭<memory>
的內容std.threading
提供標頭<atomic>
、<condition_variable>
、<future>
、<mutex>
、<shared_mutex>
和<thread>
的內容std.core
提供 C++ 標準程式庫中的所有其他內容
若要取用這些模組,請將匯入宣告新增至原始程式碼檔案的頂端。 例如:
import std.core;
import std.regex;
若要取用 Microsoft 標準程式庫模組,請使用 /EHsc
和 /MD
選項來編譯您的程式。
範例
下列範例示範名為 Example.ixx
之來源檔案中的簡單模組定義。 Visual Studio 中的模組介面檔案需要 .ixx
延伸模組。 在此範例中,介面檔案同時包含函式定義和宣告。 不過,您也可以將定義放置在一或多個個別的模組實作檔案中,如稍後範例所示。
export module Example;
陳述式指出這個檔案是稱為 Example
之模組的主要介面。 f()
上的 export
修飾元表示當另一個程式或模組匯入 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 Example;
import std.core;
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
optidentifier
module-name-qualifier-seq
:
identifier
.
module-name-qualifier-seq
identifier
.
module-partition
:
:
module-name
module-declaration
:
export
optmodule
module-name
module-partition
optattribute-specifier-seq
opt;
module-import-declaration
:
export
optimport
module-name
attribute-specifier-seq
opt;
export
optimport
module-partition
attribute-specifier-seq
opt;
export
optimport
header-name
attribute-specifier-seq
opt;
實作模組
模組介面會匯出模組名稱和構成模組公用介面的所有命名空間、類型、函式等等。
模組實作會定義模組所匯出的內容。
模組在最簡單的形式下,可以是結合模組介面和實作的單一檔案。 您也可以將實作放置在一或多個個別的模組實作檔案中,類似於 .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.core;
import MyModuleB;
//... rest of file
您可以使用傳統標頭檔來控制匯入的模組:
// MyProgram.h
import std.core;
#ifdef DEBUG_LOGGING
import std.filesystem;
#endif
匯入的標頭檔
有些標頭已足夠獨立,因此可以使用 import
關鍵字來帶入。 匯入標頭和匯入模組之間的主要差異在於,標頭中的任何前置處理器定義都會在 import
陳述式之後的匯入程式中立即顯示。
import <vector>;
import "myheader.h";
另請參閱
意見反應
https://aka.ms/ContentUserFeedback。
即將登場:在 2024 年,我們將逐步淘汰 GitHub 問題作為內容的意見反應機制,並將它取代為新的意見反應系統。 如需詳細資訊,請參閱:提交並檢視相關的意見反應