C++20 には モジュールが導入されています。 モジュールは、ソース ファイルとは独立してコンパイルされる一連のソース コード ファイルです (より正確には、それらをインポートする翻訳単位)。
モジュールは、ヘッダー ファイルの使用に関連する問題の多くを排除または削減します。 多くの場合、コンパイル時間が短縮され、場合によっては大幅に短縮されます。 モジュールで宣言されているマクロ、プリプロセッサ ディレクティブ、および nonexported 名は、モジュールの外部には表示されません。 モジュールをインポートする翻訳単位のコンパイルには影響しません。 マクロの再定義を気にすることなく、任意の順序でモジュールをインポートできます。 インポートする翻訳単位の宣言は、インポートされたモジュールのオーバーロード解決または名前参照には含まれません。 モジュールが 1 回コンパイルされると、エクスポートされたすべての型、関数、およびテンプレートを記述するバイナリ ファイルに結果が格納されます。 コンパイラはそのファイルをヘッダー ファイルよりもはるかに高速に処理できます。 また、コンパイラは、モジュールがプロジェクトにインポートされるすべての場所で再利用できます。
ヘッダー ファイルと並べてモジュールを使用できます。 C++ ソース ファイルでは、モジュールを import
したり、ヘッダー ファイルを #include
したりできます。 場合によっては、ヘッダー ファイルをモジュールとしてインポートできます。これは、 #include
を使用してプリプロセッサで処理するよりも高速です。 可能な限りヘッダー ファイルではなく、新しいプロジェクトでモジュールを使用することをお勧めします。 開発中の大規模な既存プロジェクトの場合は、レガシ ヘッダーをモジュールに変換することを試してください。 コンパイル時間が大幅に短縮されるかどうかに基づいて導入を行います。
モジュールと標準ライブラリをインポートする他の方法を比較するには、「 ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーの比較」を参照してください。
Visual Studio 2022 バージョン 17.5 以降では、標準ライブラリをモジュールとしてインポートすることは、Microsoft C++ コンパイラで標準化され、完全に実装されています。 モジュールを使用して標準ライブラリをインポートする方法については、「モジュールを 使用して C++ 標準ライブラリをインポートする」を参照してください。
単一パーティション モジュール
単一パーティション モジュールは、単一のソース ファイルで構成されるモジュールです。 モジュール インターフェイスと実装は同じファイル内にあります。
次の単一パーティション モジュールの例は、 Example.ixx
という名前のソース ファイル内の単純なモジュール定義を示しています。 .ixx
拡張機能は、Visual Studio のモジュール インターフェイス ファイルの既定の拡張機能です。 別の拡張機能を使用する場合は、 /interface スイッチを使用してモジュール インターフェイスとしてコンパイルします。 この例では、インターフェイス ファイルに関数定義と宣言の両方が含まれています。 後の例に示すように、1 つ以上の個別のモジュール実装ファイルに定義を配置することもできますが、これは単一パーティション モジュールの例です。
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
選ぶmodule
module-name
module-partition
選ぶattribute-specifier-seq
選ぶ;
module-import-declaration
:
export
選ぶimport
module-name
attribute-specifier-seq
選ぶ;
export
選ぶimport
module-partition
attribute-specifier-seq
選ぶ;
export
選ぶimport
header-name
attribute-specifier-seq
選ぶ;
モジュールの実装
モジュール インターフェイスは、モジュール名と、モジュールのパブリック インターフェイスを構成するすべての名前空間、型、関数などをエクスポートします。
モジュール実装では、 モジュール によってエクスポートされる内容を定義します。
最も単純な形式のモジュールは、モジュール インターフェイスと実装を組み合わせた 1 つのファイルにすることができます。 また、 .h
ファイルや .cpp
ファイルの実行方法と同様に、1 つ以上の個別のモジュール実装ファイルに実装を配置することもできます。
大規模なモジュールの場合は、モジュールの一部を パーティションと呼ばれるサブモジュールに分割できます。 各パーティションは、モジュール のパーティション名をエクスポートするモジュール インターフェイス ファイルで構成されます。 パーティションには、1 つ以上のパーティション実装ファイルを含めることもできます。 モジュール全体には、モジュールのパブリック インターフェイスである 1 つの プライマリ モジュール インターフェイスがあります。 必要に応じて、パーティション インターフェイスをエクスポートできます。
モジュールは、1 つ以上の モジュール ユニットで構成されます。 モジュール ユニットは、モジュール宣言を含む変換単位 (ソース ファイル) です。 モジュール ユニットにはいくつかの種類があります。
- モジュール インターフェイス ユニットは、モジュール名またはモジュール パーティション名をエクスポートします。 モジュール インターフェイス ユニットは、そのモジュール宣言に
export module
。 - モジュール実装ユニットは、モジュール名またはモジュール パーティション名をエクスポートしません。 名前が示すように、モジュールを実装します。
- プライマリ モジュール インターフェイス ユニットは、モジュール名をエクスポートします。 モジュールには、1 つのプライマリ モジュール インターフェイス ユニットが 1 つだけ存在する必要があります。
- モジュール・パーティション・インターフェース・ユニットは、モジュール・パーティション名をエクスポートします。
- モジュール パーティション実装ユニットのモジュール宣言にはモジュール パーティション名がありますが、
export
キーワードはありません。
export
キーワードは、インターフェイス ファイルでのみ使用されます。 実装ファイルは別のモジュールを import
できますが、名前を export
することはできません。 実装ファイルには、任意の拡張子を付けることができます。
モジュール、名前空間、および引数に依存する参照
モジュール内の名前空間の規則は、他のコードと同じです。 名前空間内の宣言がエクスポートされると、外側の名前空間 (その名前空間で明示的にエクスポートされていないメンバーを除く) も暗黙的にエクスポートされます。 名前空間を明示的にエクスポートすると、その名前空間定義内のすべての宣言がエクスポートされます。
コンパイラは、インポートする翻訳単位でオーバーロード解決の引数依存参照を行う場合、関数の引数の型が定義されている場所と同じ変換単位 (モジュール インターフェイスを含む) で宣言された関数を考慮します。
モジュール パーティション
モジュール パーティションは、次の点を除き、モジュールに似ています。
- モジュール全体のすべての宣言の所有権を共有します。
- パーティション インターフェイス ファイルによってエクスポートされるすべての名前は、プライマリ インターフェイス ファイルによってインポートおよびエクスポートされます。
- パーティションの名前は、モジュール名の後にコロン (
:
) が続く必要があります。 - いずれかのパーティション内の宣言は、モジュール全体に表示されます。
- 1 定義規則 (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++ 標準ライブラリをインポートする
module
、 import
、 export
名前付きモジュールのチュートリアル
ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーを比較する