共用方式為


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-seqoptidentifier

module-name-qualifier-seq
identifier .
module-name-qualifier-seq identifier .

module-partition
: module-name

module-declaration
exportoptmodulemodule-namemodule-partitionoptattribute-specifier-seqopt;

module-import-declaration
exportoptimportmodule-nameattribute-specifier-seqopt;
exportoptimportmodule-partitionattribute-specifier-seqopt;
exportoptimportheader-nameattribute-specifier-seqopt;

實作模組

模組介面會匯出模組名稱和構成模組公用介面的所有命名空間、類型、函式等等。
模組實作會定義模組所匯出的內容。
模組在最簡單的形式下,可以是結合模組介面和實作的單一檔案。 您也可以將實作放置在一或多個個別的模組實作檔案中,類似於 .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";

另請參閱

moduleimportexport
具名模組教學課程
比較標頭單位、模組和先行編譯標頭檔