C++ のモジュールの概要

C++20 には、C++ ライブラリとプログラムをコンポーネントに変換する最新のソリューションである モジュールが導入されています。 モジュールは、インポートする翻訳単位とは独立してコンパイルされる一連のソース コード ファイルです。 ヘッダー ファイルの使用に関連する問題の多くは、モジュールによって排除または軽減されます。 そして、多くの場合は、コンパイル時間が短縮されます。 モジュール内で宣言されたマクロ、プリプロセッサ ディレクティブ、およびエクスポートされていない名前は、モジュールの外部からは見えません。 それらが、モジュールをインポートする翻訳単位のコンパイルに影響を与えることはありません。 マクロの再定義を気にすることなく、モジュールを任意の順序でインポートできます。 インポートする変換単位の宣言は、インポートされたモジュールのオーバーロードの解決や名前の検索には含まれません。 モジュールが 1 回コンパイルされると、結果は、エクスポートされた型、関数、およびテンプレートを記述するバイナリ ファイルに格納されます。 コンパイラはそのファイルをヘッダー ファイルよりもはるかに高速に処理できます。 また、モジュールがプロジェクトにインポートされる場所ごとにコンパイラによって再利用できます。

モジュールは、ヘッダー ファイルと並べて使用できます。 C++ ソース ファイルでは、モジュールとヘッダー ファイルを#include使用できますimport。 場合によっては、プリプロセッサで使用 #include することで、ヘッダー ファイルをテキスト形式で含めるのではなく、モジュールとしてインポートできます。 新しいプロジェクトでは、可能な限りヘッダー ファイルではなくモジュールを使用することをお勧めします。 積極的な開発において既存のプロジェクトの規模が大きくなる場合は、レガシ ヘッダーをモジュールに変換する方法を試してください。 コンパイル時間が大幅に短縮されるかどうかに基づいて導入します。

Microsoft C++ コンパイラでモジュールを有効にする

モジュールは、Microsoft C++ コンパイラで長い間試験的にサポートされています。 Visual Studio 2022 バージョン 17.1 の時点では、C++20 標準モジュールは Microsoft C++ コンパイラに完全には実装されていません。 モジュール機能を使用すると、単一パーティション モジュールを作成し、Microsoft が提供する標準ライブラリ モジュールをインポートできます。 標準ライブラリ モジュールのサポートを有効にするには、/experimental:module/std:c++latest を使用してコンパイルします。 Visual Studio プロジェクトで、ソリューション エクスプローラーでプロジェクト ノードを右クリックし、[プロパティ] を選択します。 [構成] ドロップダウンを [すべての構成] に設定し、[構成プロパティ]>[C/C++]>[言語]>[C++ モジュールを使用できる (試験段階)] を選択します。

モジュールとそれを使用するコードは、同じコンパイラ オプションを使用してコンパイルする必要があります。

C++ 標準ライブラリをモジュールとして使用する

C++20 標準では指定されていませんが、Microsoft は 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 Standard Library モジュールを使用するには、プログラムとオプションを使用/EHsc/MDしてコンパイルします。

基本的な例

次の例は、Example.ixx というソース ファイル内の単純なモジュール定義を示しています。 .ixx 拡張子は、Visual Studio のモジュール インターフェイス ファイルに必要です。 この例では、インターフェイス ファイルに関数定義と宣言が含まれています。 ただし、後の例に示すように、1 つ以上の個別のモジュール実装ファイルに定義を配置することもできます。 export module Example; ステートメントは、このファイルが Example と呼ばれるモジュールのプライマリ インターフェイスであることを示します。 f() の修飾子 export は、Example が別のプログラムまたはモジュールによってインポートされた場合に、この関数が表示されることを示します。 モジュールは名前空間 Example_NS を参照します。

// 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:
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選ぶ;

モジュールの実装

モジュール インターフェイスは、モジュール名と、モジュールのパブリック インターフェイスを構成するすべての名前空間、型、関数などをエクスポートします。 モジュール実装は、 モジュール によってエクスポートされる内容を定義します。 最も簡単な形式では、モジュールは、モジュール インターフェイスと実装を組み合わせた 1 つのファイルで構成できます。 また、ファイルの使用方法 .h.cpp 同様に、1 つ以上の個別のモジュール実装ファイルに実装を配置することもできます。

大規模なモジュールの場合は、モジュールの一部を パーティションと呼ばれるサブモジュールに分割できます。 各パーティションは、モジュール パーティション名をエクスポートするモジュール インターフェイス ファイルで構成されます。 パーティションには、1 つ以上のパーティション実装ファイルを含めることもできます。 モジュール全体には、1 つの プライマリ モジュール インターフェイス(パーティション インターフェイスをインポートおよびエクスポートできるモジュールのパブリック インターフェイス)があります。

モジュールは、1 つ以上の モジュール ユニットで構成されます。 モジュール単位は、モジュール宣言を含む変換単位 (ソース ファイル) です。 モジュール ユニットにはいくつかの種類があります。

  • モジュール インターフェイス ユニットは、モジュール名またはモジュール パーティション名をエクスポートするモジュール ユニットです。 モジュール インターフェイス ユニットは、 export module そのモジュール宣言に含まれています。

  • モジュール実装ユニットは、モジュール名またはモジュール パーティション名をエクスポートしないモジュール ユニットです。 名前が示すように、モジュールを実装するために使用されます。

  • プライマリ モジュール インターフェイス ユニットは、モジュール名をエクスポートするモジュール インターフェイス ユニットです。 モジュールには、1 つのプライマリ モジュール インターフェイス ユニットが 1 つだけ存在する必要があります。

  • モジュール パーティション インターフェイス ユニットは、モジュール パーティション名をエクスポートするモジュール インターフェイス ユニットです。

  • モジュール パーティション実装ユニットは、モジュール宣言にモジュール パーティション名を持ち、キーワードを持たないexportモジュール実装ユニットです。

この export キーワードは、インターフェイス ファイルでのみ使用されます。 実装ファイルでは別のモジュールを import できますが、名前を export することはできません。 実装ファイルには任意の拡張子を付けることができます。

モジュール、名前空間、引数に依存する検索

モジュール内の名前空間の規則は、他のコードと同じです。 名前空間内の宣言がエクスポートされた場合、囲む名前空間 (エクスポートされていないメンバーを除く) も暗黙的にエクスポートされます。 名前空間が明示的にエクスポートされた場合、その名前空間定義内のすべての宣言がエクスポートされます。

インポート変換単位でオーバーロード解決に対して引数に依存する検索が行われると、コンパイラでは、関数の引数の型が定義されているのと同じ変換単位 (モジュール インターフェイスを含む) で宣言されている関数を考慮します。

モジュール パーティション

モジュール パーティションは、モジュール全体のすべての宣言の所有権を共有する点を除いて、モジュールに似ています。 パーティション インターフェイス ファイルによってエクスポートされた名前はすべて、プライマリ インターフェイス ファイルによってインポートおよび再エクスポートされます。 パーティションの名前は、モジュール名の後にコロンで始まる必要があります。 任意のパーティション内の宣言は、モジュール全体で表示されます。 1 つの定義規則 (ODR) エラーを回避するために特別な予防措置は必要ない。 あるパーティションで名前 (関数やクラスなど) を宣言し、別のパーティションでそれを定義することができます。 パーティション実装ファイルは次のように始まります。

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

関連項目

module, import, export
名前付きモジュールのチュートリアル