名前付きモジュールのチュートリアル (C++)
このチュートリアルでは、C++20 モジュールの作成について説明します。 モジュールはヘッダー ファイルを置き換えます。 モジュールがヘッダー ファイルをどのように改善されるかについて説明します。
このチュートリアルで学習する内容は次のとおりです:
- モジュールを作成してインポートする
- プライマリ モジュール インターフェイス ユニットを作成する
- モジュール パーティション ファイルを作成する
- モジュール ユニットの実装ファイルを作成する
前提条件
このチュートリアルでは、Visual Studio 2022 17.1.0 以降が必要です。
このチュートリアルのコード例の使用中に、IntelliSense エラーが発生する場合があります。 IntelliSense エンジンに対する作業がコンパイラに併せて進行中です。 IntelliSense エラーは無視することができ、コード例のビルドを妨げるものではありません。 IntelliSense の作業の進行状況を追跡するには、こちらのイシューを参照してください。
C++ モジュールとは
ヘッダー ファイルは、C++ のソース ファイル間で宣言と定義を共有する方法です。 ヘッダー ファイルは壊れやすく、作成が困難です。 コンパイル方法は、含める順序や、定義されているマクロまたは定義されていないマクロによって異なる場合があります。 インクルードするソース ファイルごとに再処理されるため、コンパイル速度が低下する可能性があります。
C++20 では、C++ プログラムをコンポーネント化するための最新の方法である、"モジュール" が導入されています。
ヘッダー ファイルと同様に、モジュールを使うとソース ファイル間で宣言と定義を共有できます。 しかし、ヘッダー ファイルとは異なり、モジュールではマクロ定義やプライベート実装の詳細が漏れなくなります。
モジュールは、マクロ定義や他にインポートされた内容、インポートの順序などによってセマンティクスが変更されないため、作成が容易になります。 また、コンシューマーに表示される内容を簡単に制御できます。
モジュールでは、ヘッダー ファイルにはない追加の安全性が保証されます。 コンパイラとリンカーは連携して、名前の競合の可能性を防ぎ、より強力な 1 つの定義規則 (ODR) の保証を提供します。
強力な所有権モデルでは、リンク時の名前間の衝突が回避されます。リンカーによって、エクスポートされた名前が、それらをエクスポートするモジュールにアタッチされるためです。 このモデルにより、Microsoft Visual C++ コンパイラでは、似た名前を報告する異なるモジュールを同じプログラム内でリンクすることで発生する、未定義の動作を防ぐことができます。 詳細については、「強力な所有権」を参照してください。
モジュールは、バイナリ ファイルにコンパイルされる 1 つ以上のソース コード ファイルで構成されます。 バイナリ ファイルには、モジュール内のすべてのエクスポートされた型、関数、テンプレートが記述されています。 ソース ファイルでモジュールをインポートすると、そのモジュールの内容を含むバイナリ ファイルがコンパイラによって読み取られます。 バイナリ ファイルの読み取りは、ヘッダー ファイルの処理よりもはるかに高速です。 また、バイナリ ファイルは、モジュールがインポートされるたびにコンパイラによって再利用され、さらに多くの時間を節約します。 モジュールは、インポートされるたびではなく 1 回だけビルドされるので、ビルド時間が (場合によっては大幅に) 短縮されます。
さらに重要なのは、モジュールには、ヘッダー ファイルが行う脆弱性の問題がないということです。 モジュールをインポートしても、モジュールのセマンティクスや、インポートされた他のモジュールのセマンティクスは変更されません。 モジュール内で宣言されたマクロ、プリプロセッサ ディレクティブ、エクスポートされていない名前は、それをインポートするソース ファイルからは見えません。 モジュールは任意の順序でインポートでき、モジュールの意味は変更されません。
モジュールは、ヘッダー ファイルとサイド バイ サイドで使用できます。 この機能は、モジュールを段階的に実行できるため、モジュールを使用するようにコード ベースを移行する場合に便利です。
場合によっては、ヘッダー ファイルを #include
ファイルとしてではなくヘッダー ユニットとしてインポートできます。 ヘッダー ユニットは、プリコンパイル済みヘッダー ファイル (PCH) の代替手段として推奨されます。 共有 PCH ファイルと比べて設定と使用が簡単ですが、同様のパフォーマンス効果が得られます。 詳細については、「チュートリアル: Microsoft Visual C++ でヘッダー ユニットをビルドしてインポートする」を参照してください。
コードでは、スタティック ライブラリ プロジェクトへのプロジェクト間参照を使用して、同じプロジェクトまたは参照先のプロジェクト内のモジュールを自動的に使用できます。
プロジェクトの作成
単純なプロジェクトを構築する際に、モジュールのさまざまな側面を見ていきます。 プロジェクトでは、ヘッダー ファイルではなくモジュールを使用して API を実装します。
Visual Studio 2022 以降で、[新しいプロジェクトの作成] を選択し、[コンソール アプリ (C++用)] プロジェクトの種類を選択します。 このプロジェクト タイプが使用できない場合は、Visual Studio をインストールしたときに [C++ によるデスクトップ開発] ワークロードを選択していない可能性があります。 Visual Studio インストーラーを使用して C++ ワークロードを追加できます。
新しいプロジェクトに「ModulesTutorial
」という名前を指定し、プロジェクトを作成します。
モジュールは C++20 機能であるため、または/std:c++latest
コンパイラ オプションを/std:c++20
使用します。 ソリューション エクスプローラーで、プロジェクト名ModulesTutorial
を右クリックし、[プロパティ] を選択します。 プロジェクトの [プロパティ ページ] ダイアログで、[構成] を [すべての構成] に、[プラットフォーム] を [すべてのプラットフォーム] に変更します。 左側のツリー ビュー ペインで[構成プロパティ>全般]を選択します。 C++ 言語標準プロパティを選択します。 ドロップダウンを使用して、プロパティ値を ISO C++20 Standard (/std:c++20) に変更します。 [OK] を選択して変更を確定します。
プライマリ モジュール インターフェイス ユニットを作成する
モジュールは、1 つ以上のファイルで構成されます。 これらのファイルの 1 つは、"プライマリ モジュール インターフェイス ユニット" と呼ばれるものである必要があります。 モジュールがエクスポートするものを定義します。つまり、モジュールのインポーターに表示される内容です。 モジュールごとに 1 つのプライマリ モジュール インターフェイス ユニットしか存在できません。
プライマリ モジュール インターフェイス ユニットを追加するには、ソリューション エクスプローラーで[ソース ファイル]を右クリックし、[モジュールの追加>]を選択します。
表示される [新しい項目の追加] ダイアログで、新しいモジュールに「BasicPlane.Figures.ixx
」という名前を指定して、[追加] を選択します。
作成されたモジュール ファイルの既定の内容には、次の 2 行が含まれています。
export module BasicPlane;
export void MyFunc();
export module
最初の行のキーワード (keyword)は、このファイルがモジュール インターフェイス ユニットであることを宣言します。 ここには微妙な点があります。名前付きモジュールごとに、モジュール パーティションが指定されていないモジュール インターフェイス ユニットが 1 つだけ必要です。 そのモジュール ユニットは、プライマリ モジュール インターフェイス ユニットと呼ばれます。
プライマリ モジュール インターフェイス ユニットでは、関数、型、テンプレート、他のモジュール、モジュール パーティションを宣言し、ソース ファイルがモジュールをインポートすると公開されます。 モジュールは複数のファイルで構成できますが、公開されるものを示すのはプライマリ モジュール インターフェイス ファイルだけです。
ファイルの内容を次のように BasicPlane.Figures.ixx
置き換えます。
export module BasicPlane.Figures; // the export module keywords mark this file as a primary module interface unit
この行により、このファイルがプライマリ モジュール インターフェイスとして識別され、モジュールに BasicPlane.Figures
という名前が指定されます。 モジュール名のピリオドは、コンパイラに対して特別な意味はありません。 ピリオドを使うと、モジュールがどのように構成されているかを伝えることができます。 連携する複数のモジュール ファイルがある場合は、ピリオドを使って懸念事項の分離を示します。 このチュートリアルでは、ピリオドを使って、API の異なる機能領域を示します。
この名前は、"名前付きモジュール" の "名前付き" の由来でもあります。 このモジュールの一部であるファイルは、この名前を使用して、名前付きモジュールの一部として自身を識別します。 名前付きモジュールは、同じモジュール名を持つモジュール ユニットのコレクションです。
先へ進む前に、実装する API について少し説明する必要があります。 それは次に行う選択に影響します。 API はさまざまな図形を表します。 この例では、Point
と Rectangle
という 2 つの図形のみを提供します。 Point
は、Rectangle
などのより複雑な図形の一部として使用することを意図しています。
モジュールのいくつかの機能を説明するために、この API を細かく分解しましょう。 1 つの部分が Point
API になります。 もう 1 つの部分は Rectangle
になります。 この API がさらに複雑なものに成長することを想像してください。 その分割は、懸念事項を分離したり、コードのメンテナンスを容易にしたりするのに役立ちます。
ここまでで、この API を公開するプライマリ モジュール インターフェイスを作成しました。 次に、Point
API を作成しましょう。 これをこのモジュールの一部にする必要があります。 論理的な構成や、潜在的な作成効率などの理由から、API のこの部分をそれ自体で簡単に理解できるようにする必要があります。 そのために、"モジュール パーティション" ファイルを作成します。
モジュール パーティション ファイルは、モジュールの一部 (またはコンポーネント) です。 それがユニークである点は、モジュール内でのみ、それをモジュールの独立した部分として扱えるという点です。 モジュール パーティションをモジュールの外部で使用することはできません。 モジュール パーティションは、モジュールの実装を管理しやすい部分に分割するのに役立ちます。
パーティションをプライマリ モジュールにインポートすると、その宣言はすべて、エクスポートされているかどうかに関係なく、プライマリ モジュールに表示されます。 パーティションは、名前付きモジュールに属する任意のパーティション インターフェイス、プライマリ モジュール インターフェイス、またはモジュール ユニットにインポートできます。
モジュール パーティション ファイルを作成する
Point
モジュール パーティション
モジュール パーティション ファイルを作成するには、ソリューション エクスプローラーで [ソース ファイル] を右クリックし、[モジュールの追加>] を選択します。 ファイルBasicPlane.Figures-Point.ixx
に名前を付け、[追加] を選択します。
これはモジュール パーティション ファイルであるため、ハイフンとパーティション名をモジュール名に追加しました。 この規則は、コンパイラがモジュール名に基づく名前参照規則を使用してパーティションのコンパイル済み .ifc
ファイルを検索するため、コマンド ライン ケースでコンパイラを支援します。 この方法では、明示的な /reference
コマンドライン引数を指定しなくても、モジュールに属するパーティションを見つけることができます。 また、どのファイルがどのモジュールに属しているかを簡単に確認できるため、モジュールに属するファイルを名前で整理する場合にも役立ちます。
BasicPlane.Figures-Point.ixx
の内容を次のコードで置き換えます。
export module BasicPlane.Figures:Point; // defines a module partition, Point, that's part of the module BasicPlane.Figures
export struct Point
{
int x, y;
};
ファイルは export module
で始まります。 これらのキーワード (keyword)は、プライマリ モジュール インターフェイスの開始方法でもあります。 このファイルの違いは、モジュール名の後に続くコロン (:
) とパーティション名です。 この名前付け規則により、そのファイルがモジュール "パーティション" として識別されます。 パーティションのモジュール インターフェイスが定義されているため、プライマリ モジュール インターフェイスとは見なされません。
名前 BasicPlane.Figures:Point
により、このパーティションがモジュール BasicPlane.Figures
の一部として識別されます。 (名前のピリオドはコンパイラに特別な意味を持たない点に注意してください)。 コロンは、このファイルに、モジュール BasicPlane.Figures
に属する Point
という名前のモジュール パーティションが含まれていることを示します。 このパーティションを、この名前付きモジュールの一部である他のファイルにインポートできます。
このファイルでは、export
キーワードにより struct Point
がコンシューマーに認識されるようになります。
Rectangle
モジュール パーティション
次に定義するパーティションは Rectangle
です。 前と同じ手順で別のモジュール ファイルを作成します。ソリューション エクスプローラーで[ソース ファイル]を右クリックし、[モジュールの追加>]を選択します。 ファイルに BasicPlane.Figures-Rectangle.ixx
という名前を付けて、 [追加] を選択します。
BasicPlane.Figures-Rectangle.ixx
の内容を次のコードで置き換えます。
export module BasicPlane.Figures:Rectangle; // defines the module partition Rectangle
import :Point;
export struct Rectangle // make this struct visible to importers
{
Point ul, lr;
};
// These functions are declared, but will
// be defined in a module implementation file
export int area(const Rectangle& r);
export int height(const Rectangle& r);
export int width(const Rectangle& r);
このファイルは、 export module BasicPlane.Figures:Rectangle;
モジュールの一部 BasicPlane.Figures
であるモジュール パーティションを宣言するファイルから始まります。 モジュール名に追加した :Rectangle
により、これがモジュール BasicPlane.Figures
のパーティションとして定義されます。 これは、この名前付きモジュールの一部である任意のモジュール ファイルに個別にインポートできます。
次に、import :Point;
ではモジュール パーティションのインポート方法が示されています。 import
ステートメントにより、モジュール パーティション内のすべてのエクスポートされた型、関数、テンプレートをモジュールで参照できるようになります。 モジュール名を指定する必要はありません。 ファイルの先頭に BasicPlane.Figures
があるため、コンパイラはこのファイルが export module BasicPlane.Figures:Rectangle;
モジュールに属していることを認識します。
次に、コードによって struct Rectangle
の定義と、四角形のさまざまなプロパティを返すいくつかの関数の宣言がエクスポートされます。 export
キーワードは、その後に続く内容をモジュールのコンシューマーが参照できるようにするかどうかを示します。 これを使って、関数 area
、height
、width
をモジュール外部で参照できるようにします。
モジュール パーティション内のすべての定義と宣言は、キーワード (keyword)があるかどうかに関係なく、インポートモジュールユニットにexport
表示されます。 export
キーワードは、プライマリ モジュール インターフェイスでパーティションをエクスポートするときに、定義、宣言、または typedef をモジュール外部で参照できるようにするかどうかを制御します。
モジュールのコンシューマーで名前を参照できるようにする方法はいくつかあります。
- エクスポートするそれぞれの型、関数などの前にキーワード
export
を置きます。 - たとえば
export namespace N { ... }
、名前空間の前に配置export
すると、中かっこ内で定義されているすべてのものがエクスポートされます。 ただし、モジュール内の他の場所でnamespace N { struct S {...};}
を定義する場合、モジュールのコンシューマーはstruct S
を使用できません。 使用できないのは、名前空間宣言の前にexport
が付いていないためです。同じ名前を持つ別の名前空間がある場合でも同じです。 - 型や関数などをエクスポートしないようにするには、
export
キーワードを省略します。 モジュールの一部である他のファイルには表示されますが、モジュールのインポーターには表示されません。 module :private;
を使って、プライベート モジュール パーティションの開始をマークします。 プライベート モジュール パーティションはモジュールのセクションで、宣言はそのファイルでのみ参照できます。 このモジュールをインポートするファイルや、このモジュールの一部である他のファイルでは参照できません。 これは、ファイルに対して静的にローカルなセクションと考えてください。 このセクションは、ファイル内でのみ表示されます。- インポートされたモジュールまたはモジュール パーティションを表示するには、次を使用
export import
します。 例として次のセクションに表示します。
モジュール パーティションを構成する
API の 2 つの部分を定義したので、それらをまとめて、このモジュールをインポートするファイルがそれら全体にアクセスできるようにしましょう。
すべてのモジュール パーティションは、それらが属するモジュール定義の一部として公開する必要があります。 パーティションは、プライマリ モジュール インターフェイスで公開されます。 プライマリ モジュール インターフェイスを定義する BasicPlane.Figures.ixx
ファイルを開きます。 その内容を次に置き換えます。
export module BasicPlane.Figures; // keywords export module marks this as a primary module interface unit
export import :Point; // bring in the Point partition, and export it to consumers of this module
export import :Rectangle; // bring in the Rectangle partition, and export it to consumers of this module
ここで始まる export import
2 つの行は新しい行です。 このように組み合わせると、これら 2 つのキーワード (keyword)は、指定したモジュールをインポートし、このモジュールのコンシューマーに表示するようにコンパイラに指示します。 この場合、モジュール名のコロン (:
) は、モジュール パーティションをインポートしていることを示します。
インポートされた名前には、完全なモジュール名は含まれません。 たとえば、:Point
パーティションは export module BasicPlane.Figures:Point
として宣言されています。 しかし、ここでは :Point
をインポートしています。 モジュールのプライマリ モジュール インターフェイス ファイル内にあるため、モジュール BasicPlane.Figures
名は暗黙的に指定され、パーティション名のみが指定されます。
ここまでで、プライマリ モジュール インターフェイスを定義しました。これによって使用できるようにする API サーフェスが公開されます。 しかし、area()
、height()
、または width()
は宣言しただけで、定義していません。 次に、モジュール実装ファイルを作成します。
モジュール ユニットの実装ファイルを作成する
モジュールユニットの実装ファイルは、拡張子で .ixx
終わるのは通常 .cpp
のファイルです。 モジュール ユニットの実装ファイルを追加するには、ソリューション エクスプローラーで [ソース ファイル] を右クリックし、[追加]>[新しい項目] を選択してから [C++ ファイル (.cpp)] を選択して、ソース ファイルを作成します。 新しいファイルに名前BasicPlane.Figures-Rectangle.cpp
を付け、[追加] を選択します。
モジュール パーティションの実装ファイルの名前付け規則は、パーティションの名前付け規則に従います。 ただし、実装ファイルであるため、.cpp
拡張子が付きます。
BasicPlane.Figures-Rectangle.cpp
ファイルの内容を以下に置き換えます。
module;
// global module fragment area. Put #include directives here
module BasicPlane.Figures:Rectangle;
int area(const Rectangle& r) { return width(r) * height(r); }
int height(const Rectangle& r) { return r.ul.y - r.lr.y; }
int width(const Rectangle& r) { return r.lr.x - r.ul.x; }
このファイルは module;
で始まります。これにより、"グローバル モジュール フラグメント" と呼ばれるモジュールの特別な領域が導入されます。 これは名前付きモジュールのコードの前に置き、#include
などのプリプロセッサ ディレクティブを使用できます。 グローバル モジュール フラグメント内のコードは、モジュール インターフェイスによって所有またはエクスポートされません。
ヘッダー ファイルをインクルードする場合は、通常、モジュールのエクスポートされた部分として扱われないようにします。 通常は、ヘッダー ファイルをモジュールのインターフェイスの一部にしてはならない実装の詳細としてインクルードします。 それを行いたい高度なケースも存在する場合がありますが、通常は行いません。 グローバル モジュール フラグメントの #include
ディレクティブに対して、個別のメタデータ (.ifc
ファイル) は生成されません。 グローバル モジュール フラグメントは、windows.h
や、Linux の場合は unistd.h
などのヘッダー ファイルをインクルードするのに適した場所です。
ここで構築するモジュール実装ファイルでは、ライブラリをインクルードしません。実装の一部として必要ないためです。 しかし、必要な場合は、この領域に #include
ディレクティブを置きます。
この行 module BasicPlane.Figures:Rectangle;
は、このファイルが名前付きモジュール BasicPlane.Figures
の一部であることを示します。 コンパイラにより、プライマリ モジュール インターフェイスによって公開されている型と関数が自動的にこのファイルに取り込まれます。 モジュール実装ユニットには、モジュール宣言のexport
キーワード (keyword)の前にmodule
キーワード (keyword)がありません。
次は、関数 area()
、height()
、width()
の定義です。 これらは BasicPlane.Figures-Rectangle.ixx
の Rectangle
パーティションで宣言されていました。 このモジュールのプライマリ モジュール インターフェイスでは Point
および Rectangle
モジュール パーティションがインポートされているため、モジュール ユニットの実装ファイル内でこれらの型を参照できます。 モジュール実装ユニットの興味深い機能は、対応するモジュールのプライマリ インターフェイス内のすべてが、コンパイラによって自動的に、ファイルから参照できるようになる点です。 imports <module-name>
は不要です。
実装ユニット内で宣言するものはすべて、それが属しているモジュールでのみ参照できます。
モジュールのインポート
次に、定義したモジュールを使用します。 ModulesTutorial.cpp
ファイルを開きます。 これは、プロジェクトの一部として自動的に作成されています。 現在は、関数 main()
が含まれています。 その内容を次に置き換えます。
#include <iostream>
import BasicPlane.Figures;
int main()
{
Rectangle r{ {1,8}, {11,3} };
std::cout << "area: " << area(r) << '\n';
std::cout << "width: " << width(r) << '\n';
return 0;
}
このステートメント import BasicPlane.Figures;
により、モジュールからエクスポートされたすべての関数と型がこの BasicPlane.Figures
ファイルに表示されます。 ディレクティブの前または後 #include
に記述できます。
次に、このアプリはモジュールの型と関数を使って、定義した四角形の面積と幅を出力します。
area: 50
width: 10
モジュールの構造
次に、さまざまなモジュール ファイルについて詳しく見てみます。
プライマリ モジュール インターフェイス
モジュールは、1 つ以上のファイルで構成されます。 そのうちの 1 つでは、インポート先が参照できるインターフェイスを定義します。 このファイルには、"プライマリ モジュール インターフェイス" が含まれます。 モジュールごとに使用できるプライマリ モジュール インターフェイスは 1 つのみです。 前述のように、エクスポートされたモジュール インターフェイス ユニットでは、モジュール パーティションは指定されません。
これは既定で .ixx
拡張子を持ちます。 ただし、任意の拡張子を持つソース ファイルをモジュール インターフェイス ファイルとして扱うことができます。 これを行うには、ソース ファイルのプロパティ ページの [詳細設定] タブの [コンパイル先] プロパティを [モジュールとしてコンパイル] (/interface) に設定します。
モジュール インターフェイス定義ファイルの基本的な概要は、次のようになります。
module; // optional. Defines the beginning of the global module fragment
// #include directives go here but only apply to this file and
// aren't shared with other module implementation files.
// Macro definitions aren't visible outside this file, or to importers.
// import statements aren't allowed here. They go in the module preamble, below.
export module [module-name]; // Required. Marks the beginning of the module preamble
// import statements go here. They're available to all files that belong to the named module
// Put #includes in the global module fragment, above
// After any import statements, the module purview begins here
// Put exported functions, types, and templates here
module :private; // optional. The start of the private module partition.
// Everything after this point is visible only within this file, and isn't
// visible to any of the other files that belong to the named module.
このファイルは、グローバル モジュール フラグメントの開始を示す場合は module;
で、または "モジュール purview" の開始を示す場合は export module [module-name];
で始める必要があります。
モジュール purview は、関数、型、テンプレートなどがモジュールから公開する場所です。
また、BasicPlane.Figures.ixx
ファイルに示すように、export import
キーワードを使って他のモジュールやモジュール パーティションを公開することもできます。
プライマリ インターフェイス ファイルは、モジュールに対して直接または間接的に定義されているすべてのインターフェイス パーティションをエクスポートする必要があります。または、プログラムの形式が正しくありません。
プライベート モジュール パーティションは、このファイル内でのみ参照できるようにしたいものを置く場所です。
モジュール インターフェイス ユニットでは、キーワード module
の前にキーワード export
を付けます。
モジュールの構文の詳細については、モジュールに関する記事を参照してください。
モジュール実装ユニット
モジュール実装ユニットは、名前付きモジュールに属しています。 それらが属する名前付きモジュールは、ファイル内の module [module-name]
ステートメントによって示されます。 モジュール実装ユニットでは、コードの安全性やその他の理由などにより、プライマリ モジュール インターフェイスやモジュール パーティション ファイルに含めたくない実装の詳細を提供します。
モジュール実装ユニットは、大きなモジュールをより小さな部分に分割する場合に役立ち、ビルド時間が短縮される可能性があります。 この手法については、ベスト プラクティスに関するセクションで簡単に説明します。
モジュール実装ユニット ファイルは .cpp
拡張子を持ちます。 モジュール実装ユニット ファイルの基本的な概要は次のとおりです。
// optional #include or import statements. These only apply to this file
// imports in the associated module's interface are automatically available to this file
module [module-name]; // required. Identifies which named module this implementation unit belongs to
// implementation
モジュール パーティション ファイル
モジュール パーティションは、モジュールを異なる部分 (パーティション ) にコンポーネント化する方法を提供します。 モジュール パーティションは、名前付きモジュールの一部であるファイルにのみインポートされることを意図しています。 名前付きモジュールの外部でインポートすることはできません。
パーティションには、インターフェイス ファイルと、0 個以上の実装ファイルがあります。 モジュール パーティションは、モジュール全体のすべての宣言の所有権を共有します。
パーティション インターフェイス ファイルによってエクスポートされた名前はすべて、プライマリ インターフェイス ファイルによってインポートおよび再エクスポート (export import
) される必要があります。 パーティションの名前はモジュール名で始まり、その後にコロンと、パーティションの名前を続ける必要があります。
パーティション インターフェイス ファイルの基本的な概要は、次のようになります。
module; // optional. Defines the beginning of the global module fragment
// This is where #include directives go. They only apply to this file and aren't shared
// with other module implementation files.
// Macro definitions aren't visible outside of this file or to importers
// import statements aren't allowed here. They go in the module preamble, below
export module [Module-name]:[Partition name]; // Required. Marks the beginning of the module preamble
// import statements go here.
// To access declarations in another partition, import the partition. Only use the partition name, not the module name.
// For example, import :Point;
// #include directives don't go here. The recommended place is in the global module fragment, above
// export imports statements go here
// after import, export import statements, the module purview begins
// put exported functions, types, and templates for the partition here
module :private; // optional. Everything after this point is visible only within this file, and isn't
// visible to any of the other files that belong to the named module.
...
モジュールのベスト プラクティス
モジュールとそれをインポートするコードは、同じコンパイラ オプションを使用してコンパイルする必要があります。
モジュールの名前付け
- モジュール名にはピリオド ('.') を使用できますが、コンパイラには特別な意味はありません。 それらを使って、モジュールの意味をユーザーに伝えます。 たとえば、ライブラリまたはプロジェクトの上位の名前空間から開始します。 最後にモジュールの機能を説明する名前を指定します。
BasicPlane.Figures
は、幾何平面用の、特に平面上で表現できる図形用の API を伝えることを意図しています。 - 通常、モジュールのプライマリ インターフェイスを含むファイルの名前は、モジュールの名前です。 たとえば、モジュール名が
BasicPlane.Figures
だとすると、プライマリ インターフェイスを含むファイルの名前はBasicPlane.Figures.ixx
になります。 - 通常、モジュール パーティション ファイルの名前は
<primary-module-name>-<module-partition-name>
となり、モジュールの名前の後にハイフン ('-') とパーティションの名前が続きます。 たとえば、BasicPlane.Figures-Rectangle.ixx
のように指定します。
コマンド ラインからビルドを行い、モジュール パーティションに対してこの名前付け規則を使う場合は、モジュール パーティション ファイルごとに /reference
を明示的に追加する必要はありません。 コンパイラによって、モジュールの名前に基づいてそれらが自動的に検索されます。 コンパイル済みパーティション ファイルの名前 (拡張子で .ifc
終わる) は、モジュール名から生成されます。 モジュール名 BasicPlane.Figures:Rectangle
を考えてみましょう。コンパイラは、対応するコンパイル済みパーティション ファイル Rectangle
の名前が指定 BasicPlane.Figures-Rectangle.ifc
されていることを予測します。 コンパイラは、この名前付けスキームを使用して、パーティションのインターフェイス ユニット ファイルを自動的に見つけることで、モジュール パーティションを簡単に使用できるようにします。
独自の規則を使用して名前を付けることができます。 ただし、その場合は、コマンドライン コンパイラに対応する /reference
引数を指定する必要があります。
係数モジュール
モジュール実装ファイルとパーティションを使ってモジュールを分解して、コードのメンテナンスを容易にし、場合によってはコンパイル時間を短縮します。
たとえば、モジュールの実装をモジュール インターフェイス定義ファイルからモジュール実装ファイルに移動することは、実装を変更しても、そのモジュールをインポートするすべてのファイルが必ずしも再コンパイルされるとは限らない (inline
実装がない限り) ことを意味します。
モジュール パーティションを使用すると、大規模なモジュールを論理的に分解しやすくなります。 それらを使ってコンパイル時間を短縮し、実装の一部を変更しても、モジュールのすべてのファイルが再コンパイルされないようにすることができます。
まとめ
このチュートリアルでは、C++20 モジュールの基本について説明しました。 プライマリ モジュール インターフェイスを作成し、モジュール パーティションを定義し、モジュール実装ファイルを構築しました。
関連項目
C++ のモジュールの概要
module
、import
、export
キーワード
Visual Studio での C++ モジュールのツアー
実用的な C++20 モジュールと C++ モジュールに関するツールの将来
C++ 名前付きモジュールにプロジェクトを移行する
チュートリアル: Microsoft Visual C++ でヘッダー ユニットをビルドしてインポートする
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示