C++ ライブラリ モジュールを使用して C++ 標準ライブラリをインポートする方法について説明します。 これにより、コンパイルが高速になり、ヘッダー ファイルやヘッダー ユニット、プリコンパイル済みヘッダー (PCH) を使用するよりも堅牢になります。
このチュートリアルでは、以下について学習します。
- コマンド ラインから標準ライブラリをモジュールとしてインポートする方法。
- モジュールのパフォーマンスと使いやすさの利点。
- 2 つの標準ライブラリ モジュール
stdとstd.compat、それらの違いについて説明します。
[前提条件]
このチュートリアルでは、Visual Studio 2022 17.5 以降が必要です。
標準ライブラリ モジュールの概要
ヘッダー ファイルのセマンティクスは、マクロ定義、それらを含める順序、およびコンパイルの速度に応じて変更できます。 モジュールはこれらの問題を解決します。
ヘッダー ファイルのもつれとしてではなく、標準ライブラリをモジュールとしてインポートできるようになりました。 これは、ヘッダー ファイル、ヘッダー ユニット、またはプリコンパイル済みヘッダー (PCH) を含むよりもはるかに高速で堅牢です。
C++23 標準ライブラリには、 std と std.compatという 2 つの名前付きモジュールが導入されています。
-
stdは、stdなど、C++ 標準ライブラリ名前空間std::vectorで定義されている宣言と名前をエクスポートします。 また、<cstdio>などの関数を提供する<cstdlib>やstd::printf()などの C ラッパー ヘッダーの内容もエクスポートします。 グローバル名前空間で定義されている C 関数 (::printf()など) はエクスポートされません。 これにより、<cstdio>also などの C ラッパー ヘッダーを含めると、stdio.hなどの C ヘッダー ファイルが含まれ、C グローバル名前空間のバージョンが組み込まれる状況が改善されます。 これは、stdをインポートしても問題ありません。 -
std.compatは、std内のすべてをエクスポートし、::printf、::fopen、::size_t、::strlenなどの C ランタイム グローバル名前空間を追加します。std.compatモジュールを使用すると、グローバル名前空間の多くの C ランタイム関数/型を参照するコードベースを簡単に操作できます。
コンパイラは、 import std; または import std.compat; を使用すると標準ライブラリ全体をインポートし、1 つのヘッダー ファイルを取り込むよりも高速になります。 たとえば、import std;するよりも、import std.compat (または#include <vector>) を使用して標準ライブラリ全体を取り込む方が高速です。
名前付きモジュールではマクロが公開されないため、 assert、 errno、 offsetof、 va_argなどのマクロは、 std または std.compatをインポートするときに使用できません。 回避策については、 標準ライブラリの名前付きモジュールに関する考慮事項 を参照してください。
C++ モジュールについて
ヘッダー ファイルは、C++ のソース ファイル間で宣言と定義がどのように共有されているかを示します。 標準ライブラリ モジュールの前に、必要な標準ライブラリの一部を、 #include <vector>などのディレクティブと共に含めます。 ヘッダー ファイルは脆弱であり、構成が困難です。これは、ヘッダー ファイルを含める順序や、特定のマクロが定義されているかどうかによってセマンティクスが変わる可能性があるためです。 また、コンパイルが遅くなります。これは、それらを含むすべてのソース ファイルによって再処理されるためです。
C++20 では、モジュールと呼ばれる最新の代替手段 が導入されています。 C++23 では、モジュールのサポートを活用して、標準ライブラリを表す名前付きモジュールを導入することができました。
ヘッダー ファイルと同様に、モジュールを使用すると、ソース ファイル間で宣言と定義を共有できます。 ただし、ヘッダー ファイルとは異なり、モジュールは脆弱ではなく、マクロ定義やインポート順序によってセマンティクスが変更されないため、作成が容易になります。 コンパイラは、 #include ファイルを処理するよりもはるかに高速にモジュールを処理でき、コンパイル時にも使用されるメモリが少なくなります。 名前付きモジュールでは、マクロ定義やプライベート実装の詳細は公開されません。
この記事では、標準ライブラリを使用するための新しい最適な方法について説明します。 標準ライブラリを使用する別の方法の詳細については、「 ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーの比較」を参照してください。
標準ライブラリをインポートする std
次の例では、コマンド ライン コンパイラを使用して標準ライブラリをモジュールとして使用する方法を示します。 Visual Studio IDE でこれを行う方法については、「 ビルド ISO C++23 標準ライブラリ モジュール」を参照してください。
ステートメント import std; または import std.compat; は、標準ライブラリをアプリケーションにインポートします。 ただし、最初に、モジュールという名前の標準ライブラリをバイナリ形式にコンパイルする必要があります。 次の手順では、その方法を示します。
例: ビルドとインポートの方法 std
VS 用の x86 Native Tools コマンド プロンプトを開きます。Windows の [スタート] メニューから「 x86 native 」と入力すると、アプリの一覧にプロンプトが表示されます。 プロンプトが Visual Studio 2022 バージョン 17.5 以降であることを確認します。 間違ったバージョンのプロンプトを使用するとエラーが発生します。 このチュートリアルで使用する例は、CMD シェル用です。
%USERPROFILE%\source\repos\STLModulesなどのディレクトリを作成し、現在のディレクトリにします。 書き込みアクセス権がないディレクトリを選択すると、コンパイル中にエラーが発生します。次のコマンドを使用して、
std名前付きモジュールをコンパイルします。cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx"エラーが発生した場合は、正しいバージョンのコマンド プロンプトを使用していることを確認してください。
ビルドされたモジュールをインポートするコードで使用するのと同じコンパイラ設定を使用して、
std名前付きモジュールをコンパイルします。 マルチプロジェクト ソリューションがある場合は、モジュールという名前の標準ライブラリを 1 回コンパイルし、/referenceコンパイラ オプションを使用してすべてのプロジェクトから参照できます。前のコンパイラ コマンドを使用して、コンパイラは次の 2 つのファイルを出力します。
-
std.ifcは、import std;ステートメントを処理するためにコンパイラが参照する名前付きモジュール インターフェイスのコンパイル済みバイナリ表現です。 これはコンパイル時のみの成果物です。 それはあなたのアプリケーションに付属していません。 -
std.objには、名前付きモジュールの実装が含まれています。 標準ライブラリから使用する機能をアプリケーションに静的にリンクするようにサンプル アプリをコンパイルするときに、std.objをコマンド ラインに追加します。
この例の主要なコマンド ライン スイッチは次のとおりです。
スイッチ Meaning /std:c++latest最新バージョンの C++ 言語標準およびライブラリを使用します。 モジュールのサポートは /std:c++20で利用できますが、標準ライブラリの名前付きモジュールのサポートを受けるためには、最新の標準ライブラリが必要です。/EHscextern "C"マークされた関数を除き、C++ 例外処理を使用します。/W4/W4を使用することをお勧めします。特に新しいプロジェクトでは、すべてのレベル 1、レベル 2、レベル 3、およびほとんどのレベル 4 (情報) 警告が有効になり、潜在的な問題を早期にキャッチするのに役立ちます。 基本的には、lintのような警告が提供され、見つけにくいコードの欠陥を最小限に確保するのに役立ちます。/cこの時点でバイナリの名前付きモジュール インターフェイスを構築しているだけなので、リンクせずにコンパイルします。 次のスイッチを使用して、オブジェクト ファイル名と名前付きモジュール インターフェイス ファイル名を制御できます。
-
/Foは、オブジェクト ファイルの名前を設定します。 たとえば、「/Fo:"somethingelse"」のように入力します。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx) と同じ名前をオブジェクト ファイルに使用します。 この例では、モジュール ファイルのstd.objをコンパイルしているため、オブジェクト ファイル名は既定でstd.ixxされています。 -
/ifcOutputは、名前付きモジュール インターフェイス ファイル (.ifc) の名前を設定します。 たとえば、「/ifcOutput "somethingelse.ifc"」のように入力します。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ifc) と同じ名前をモジュール インターフェイス ファイル (.ixx) に使用します。 この例では、モジュール ファイルifcをコンパイルしているため、生成されたstd.ifcファイルは既定でstd.ixxされています。
-
最初に次の内容を含む
stdという名前のファイルを作成して、ビルドしたimportExample.cppライブラリをインポートします。// requires /std:c++latest import std; int main() { std::cout << "Import the STL library for best performance\n"; std::vector<int> v{5, 5, 5}; for (const auto& e : v) { std::cout << e; } }前のコードでは、
import std;は#include <vector>と#include <iostream>を置き換えます。import std;ステートメントを使用すると、すべての標準ライブラリを 1 つのステートメントで使用できるようになります。 標準ライブラリ全体のインポートは、多くの場合、#include <vector>などの 1 つの標準ライブラリ ヘッダー ファイルを処理するよりもはるかに高速です。前の手順と同じディレクトリで次のコマンドを使用して、例をコンパイルします。
cl /c /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp link importExample.obj std.objコンパイラは、
/reference "std=std.ifc"ステートメントで指定されたモジュール名と一致する.ifcファイルを自動的に検索するため、この例のコマンド ラインでimportを指定する必要はありません。 コンパイラでimport std;が見つかると、ソース コードと同じディレクトリにあるstd.ifcを見つけることができます。.ifcファイルがソース コードとは異なるディレクトリにある場合は、/referenceコンパイラ スイッチを使用して参照します。この例では、ソース コードをコンパイルし、モジュールの実装をアプリケーションにリンクする手順は別です。 必要はありません。
cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.objを使用して、1 つの手順でコンパイルとリンクを行います。 ただし、モジュールという名前の標準ライブラリを 1 回だけビルドし、ビルドのリンクステップでプロジェクトまたは複数のプロジェクトから参照できるため、個別にビルドしてリンクすると便利な場合があります。1 つのプロジェクトをビルドする場合は、
std標準ライブラリのモジュールをビルドする手順と、コマンド ラインに"%VCToolsInstallDir%\modules\std.ixx"を追加してアプリケーションをビルドする手順を組み合わせることができます。.cppモジュールを使用するstdファイルの前に配置します。既定では、出力実行可能ファイルの名前は最初の入力ファイルから取得されます。
/Feコンパイラ オプションを使用して、必要な実行可能ファイル名を指定します。 このチュートリアルでは、モジュールという名前の標準ライブラリを 1 回ビルドするだけで済み、プロジェクトまたは複数のプロジェクトから参照できるため、名前付きモジュールstdを個別の手順としてコンパイルする方法について説明します。 ただし、次のコマンド ラインに示すように、すべてを一緒に構築すると便利な場合があります。cl /FeimportExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" importExample.cpp前のコマンド ラインを指定すると、コンパイラは
importExample.exeという名前の実行可能ファイルを生成します。 これを実行すると、次の出力が生成されます。Import the STL library for best performance 555
標準ライブラリとグローバル C 関数をインポートする std.compat
C++ 標準ライブラリには、ISO C 標準ライブラリが含まれています。
std.compat モジュールは、std、std::vector、std::cout、std::printfなど、std::scanf モジュールのすべての機能を提供します。 ただし、 ::printf、 ::scanf、 ::fopen、 ::size_tなど、これらの関数のグローバル名前空間バージョンも提供されます。
std.compat名前付きモジュールは、グローバル名前空間の C ランタイム関数を参照する既存のコードの移行を容易にするために提供される互換性レイヤーです。 グローバル名前空間に名前を追加しないようにするには、 import std;を使用します。 多くの非修飾 (グローバル名前空間) C ランタイム関数を使用するコードベースの移行を容易にする必要がある場合は、 import std.compat;を使用します。 これにより、グローバル名前空間 C ランタイム名が提供されるため、すべてのグローバル名を std::で修飾する必要はありません。 グローバル名前空間 C ランタイム関数を使用する既存のコードがない場合は、 import std.compat;を使用する必要はありません。 コード内でいくつかの C ランタイム関数のみを呼び出す場合は、 import std; を使用し、 std::で必要ないくつかのグローバル名前空間 C ランタイム名を修飾することをお勧めします。 たとえば、「 std::printf() 」のように入力します。 コードをコンパイルしようとしたときに error C3861: 'printf': identifier not found のようなエラーが表示される場合は、 import std.compat; を使用してグローバル名前空間 C ランタイム関数をインポートすることを検討してください。
例: ビルドとインポートの方法 std.compat
import std.compat;を使用する前に、ソース コード形式のモジュール インターフェイス ファイルをstd.compat.ixxでコンパイルする必要があります。 Visual Studio には、プロジェクトに一致するコンパイラ設定を使用してモジュールをコンパイルできるように、モジュールのソース コードが付属しています。 この手順は、 std 名前付きモジュールをビルドする場合と似ています。
std名前付きモジュールは、std.compatが依存しているため、最初にビルドされます。
VS 用のネイティブ ツール コマンド プロンプトを開きます。Windows の [スタート ] メニューから 「x86 native 」と入力すると、アプリの一覧にプロンプトが表示されます。 プロンプトが Visual Studio 2022 バージョン 17.5 以降であることを確認します。 間違ったバージョンのプロンプトを使用すると、コンパイラ エラーが発生します。
%USERPROFILE%\source\repos\STLModulesなど、この例を試すディレクトリを作成し、現在のディレクトリにします。 書き込みアクセス権がないディレクトリを選択すると、エラーが発生します。次のコマンドを使用して、名前付きモジュール
stdとstd.compatをコンパイルします。cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx"stdおよびstd.compatは、インポートするコードで使用するコンパイラ設定と同じ設定を使用してコンパイルする必要があります。 マルチプロジェクト ソリューションがある場合は、それらを 1 回コンパイルし、/referenceコンパイラ オプションを使用してすべてのプロジェクトから参照できます。エラーが発生した場合は、正しいバージョンのコマンド プロンプトを使用していることを確認してください。
コンパイラは、前の 2 つの手順から 4 つのファイルを出力します。
-
std.ifcは、import std;ステートメントを処理するためにコンパイラが参照するコンパイル済みのバイナリ名付きモジュール インターフェイスです。std.ifcはimport std.compat;上に構築されているため、コンパイラはstd.compatを処理するstdも参照します。 これはコンパイル時のみの成果物です。 それはあなたのアプリケーションに付属していません。 -
std.objには標準ライブラリの実装が含まれています。 -
std.compat.ifcは、import std.compat;ステートメントを処理するためにコンパイラが参照するコンパイル済みのバイナリ名付きモジュール インターフェイスです。 これはコンパイル時のみの成果物です。 それはあなたのアプリケーションに付属していません。 -
std.compat.objには実装が含まれています。 ただし、ほとんどの実装はstd.objによって提供されます。 サンプル アプリをコンパイルするときにコマンド ラインにstd.objを追加して、標準ライブラリから使用する機能をアプリケーションに静的にリンクします。
次のスイッチを使用して、オブジェクト ファイル名と名前付きモジュール インターフェイス ファイル名を制御できます。
-
/Foは、オブジェクト ファイルの名前を設定します。 たとえば、「/Fo:"somethingelse"」のように入力します。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx) と同じ名前をオブジェクト ファイルに使用します。 この例では、モジュール ファイルのstd.objとstd.compat.objをコンパイルするため、オブジェクト ファイル名は既定でstd.ixxされ、std.compat.ixxされています。 -
/ifcOutputは、名前付きモジュール インターフェイス ファイル (.ifc) の名前を設定します。 たとえば、「/ifcOutput "somethingelse.ifc"」のように入力します。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ifc) と同じ名前をモジュール インターフェイス ファイル (.ixx) に使用します。 この例では、生成されたifcファイルは既定でstd.ifcされ、std.compat.ifcされます。これは、モジュール ファイルをstd.ixxしてstd.compat.ixxコンパイルするためです。
-
次の内容を含む
std.compatという名前のファイルを最初に作成して、stdCompatExample.cppライブラリをインポートします。import std.compat; int main() { printf("Import std.compat to get global names like printf()\n"); std::vector<int> v{5, 5, 5}; for (const auto& e : v) { printf("%i", e); } }前のコードでは、
import std.compat;は#include <cstdio>と#include <vector>を置き換えます。import std.compat;ステートメントを使用すると、標準ライブラリと C ランタイム関数を 1 つのステートメントで使用できます。 C++ 標準ライブラリと C ランタイム ライブラリのグローバル名前空間関数を含むこの名前付きモジュールのインポートは、#includeのような単一の#include <vector>を処理するよりも高速です。次のコマンドを使用して、例をコンパイルします。
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.objstd.compat.ifcステートメントでモジュール名と一致する.ifcファイルがコンパイラによって自動的に検索されるため、コマンド ラインでimportを指定する必要はありませんでした。 コンパイラがimport std.compat;に遭遇すると、ソースコードと同じディレクトリに配置されているため、std.compat.ifcを見つけます。これにより、コマンドラインで指定する必要がなくなります。.ifcファイルがソース コードとは異なるディレクトリにある場合、または名前が異なる場合は、/referenceコンパイラ スイッチを使用して参照します。std.compatをインポートするときは、std.compatとstd.objの両方に対してリンクする必要があります。std.compatはstd.objでコードを使用するためです。1 つのプロジェクトをビルドする場合は、コマンド ラインに
stdとstd.compatを (その順序で) 追加することで、モジュールという名前の"%VCToolsInstallDir%\modules\std.ixx"と"%VCToolsInstallDir%\modules\std.compat.ixx"標準ライブラリをビルドする手順を組み合わせることができます。 このチュートリアルでは、モジュールという名前の標準ライブラリを 1 回だけビルドするだけで済み、プロジェクトまたは複数のプロジェクトから参照できるため、標準ライブラリ モジュールを別の手順としてビルドする方法について説明します。 ただし、すべてを一度にビルドするのが便利な場合は、それらを使用する.cppファイルの前に配置し、次の例に示すようにビルド/Feに名前を付けるexeを指定してください。cl /c /FestdCompatExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx" stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.objこの例では、ソース コードをコンパイルし、モジュールの実装をアプリケーションにリンクする手順は別です。 彼らである必要はありません。
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.objを使用して、1 つの手順でコンパイルとリンクを行います。 ただし、モジュールという名前の標準ライブラリを 1 回だけビルドし、ビルドのリンクステップでプロジェクトまたは複数のプロジェクトから参照できるため、個別にビルドしてリンクすると便利な場合があります。前のコンパイラ コマンドは、
stdCompatExample.exeという名前の実行可能ファイルを生成します。 これを実行すると、次の出力が生成されます。Import std.compat to get global names like printf() 555
標準ライブラリの名前付きモジュールに関する考慮事項
名前付きモジュールのバージョン管理は、ヘッダーの場合と同じです。
.ixxの名前付きモジュール ファイルはヘッダーと共にインストールされます。たとえば、"%VCToolsInstallDir%\modules\std.ixx"。この記述時に使用したツールのバージョンでC:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixxに解決されます。 使用するヘッダー ファイルのバージョンを選択するのと同じ方法で、名前付きモジュールのバージョンを選択します。参照するディレクトリを使用します。
インポートヘッダーユニットと名前付きモジュールを混在させないでください。 たとえば、同じファイルに import <vector>; して import std; しないでください。
C++ 標準ライブラリ ヘッダー ファイルと、名前付きモジュールの std または std.compatのインポートを混在させないでください。 たとえば、同じファイルに #include <vector> して import std; しないでください。 ただし、C ヘッダーを含め、名前付きモジュールを同じファイルにインポートできます。 たとえば、同じファイルに import std; して #include <math.h> できます。 C++ 標準ライブラリバージョンの <cmath>は含めないでください。
モジュールを複数回インポートしないように保護する必要はありません。 つまり、モジュールでスタイル ヘッダー ガード #ifndef 必要はありません。 コンパイラは、名前付きモジュールがいつ既にインポートされたかを認識し、重複する試行を無視します。
assert() マクロを使用する必要がある場合は、#include <assert.h>。
errno マクロを使用する必要がある場合は、#include <errno.h>します。 名前付きモジュールではマクロが公開されないため、たとえば、 <math.h>からのエラーを確認する必要がある場合の回避策です。
NAN、INFINITY、INT_MINなどのマクロは、含めることができる<limits.h>によって定義されます。 ただし、import std;場合は、std::numeric_limits<double>::quiet_NaN()やstd::numeric_limits<double>::infinity()の代わりにNANとINFINITYを使用し、std::numeric_limits<int>::min()の代わりにINT_MINできます。
概要
このチュートリアルでは、モジュールを使用して標準ライブラリをインポートしました。 次に、 C++ の Named modules チュートリアルで独自のモジュールを作成およびインポートする方法について説明します。
こちらも参照ください
ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーを比較する
C++ のモジュールの概要
Visual Studio での C++ モジュールのツアー
Modules という名前の C++ へのプロジェクトの移動