C++ のモジュールの概要
C++20 では "モジュール" が導入されています。 モジュールは、一連のソース コード ファイルであり、ソース ファイル (正確には、それらをインポートする翻訳単位) とは別にコンパイルされます。
ヘッダー ファイルの使用に関連する問題の多くは、モジュールによって排除または軽減されます。 これにより多くの場合、コンパイル時間が (時には大幅に) 短縮されます。 モジュール内で宣言されたマクロ、プリプロセッサ ディレクティブ、およびエクスポートされていない名前は、モジュールの外部からは見えません。 それらが、モジュールをインポートする翻訳単位のコンパイルに影響を与えることはありません。 マクロの再定義を気にすることなく、モジュールを任意の順序でインポートできます。 インポートする変換単位の宣言は、インポートされたモジュールのオーバーロードの解決や名前の検索には含まれません。 モジュールが 1 回コンパイルされると、結果は、エクスポートされた型、関数、およびテンプレートを記述するバイナリ ファイルに格納されます。 コンパイラではそのファイルを、ヘッダー ファイルよりも高速に処理できます。 また、モジュールがプロジェクトにインポートされる場所ごとにコンパイラによって再利用できます。
モジュールは、ヘッダー ファイルとサイド バイ サイドで使用できます。 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
というソース ファイル内の単純なモジュール定義を示しています。 .ixx
拡張子は、Visual Studio のモジュール インターフェイス ファイルに必要です。 この例では、インターフェイス ファイルに関数定義と宣言が含まれています。 ただし、定義は 1 つ以上の個別のモジュール実装ファイルに配置することもできます (後の例を参照)。
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;
モジュールの実装
"モジュール インターフェイス" は、モジュール名と、モジュールのパブリック インターフェイスを構成するすべての名前空間、型、関数などをエクスポートします。
"モジュール実装" は、モジュール によってエクスポートされるものを定義します。
最も単純な形式として、モジュールは、モジュール インターフェイスと実装を組み合わせた 1 つのファイルにすることができます。 また、.h
ファイルと .cpp
ファイルで行う方法と同様に、1 つ以上の個別のモジュール実装ファイルに実装を入れることもできます。
大規模なモジュールの場合は、パーティションと呼ばれるサブモジュールにモジュールを分割できます。 各パーティションは、モジュールのパーティション名をエクスポートする 1 つのモジュール インターフェイス ファイルで構成されます。 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.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
名前付きモジュールのチュートリアル
ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーを比較する
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示