共用方式為


C++ 中的模組概觀

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-seq identifier .

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";

另請參閱

使用模組匯入C++標準連結庫
moduleimportexport
具名模組教學
比較標頭單位、模組和先行編譯標頭