チュートリアル: コマンド ラインからモジュールを使用して C++ 標準ライブラリ (STL) をインポートする
C++ ライブラリ モジュールを使用して C++ 標準ライブラリをインポートする方法について説明します。 これにより、コンパイルが高速になり、ヘッダー ファイルやヘッダー ユニット、プリコンパイル済みヘッダー (PCH) を使用するよりも堅牢になります。
このチュートリアルで学習する内容は次のとおりです:
- コマンド ラインから標準ライブラリをモジュールとしてインポートする方法。
- モジュールのパフォーマンスと使いやすさの利点。
- 2 つの標準ライブラリ モジュール
std
とstd.compat
とそれらの違い。
前提条件
このチュートリアルでは、Visual Studio 2022 17.5 以降が必要です。
標準ライブラリ モジュールの概要
ヘッダー ファイルは、マクロ定義、それらを含める順序、およびコンパイルの速度が遅い場合に応じて変更できるセマンティクスに影響を受けます。 モジュールはこれらの問題を解決します。
ヘッダー ファイルのもつれとしてではなく、標準ライブラリをモジュールとしてインポートできるようになりました。 これは、ヘッダー ファイルやヘッダー ユニット、プリコンパイル済みヘッダー (PCH) を含むよりもはるかに高速で堅牢です。
C++23 標準ライブラリには、次の 2 つの名前付きモジュールが導入されています: std
と std.compat
:
std
は、C++ 標準ライブラリ名前空間std
で定義されている宣言と名前をエクスポートし、例えばstd::vector
です。 また、std::printf()
などの関数を提供する<cstdio>
や<cstdlib>
などの C ラッパー ヘッダーの内容もエクスポートします。 グローバル名前空間で定義されている C 関数 (例:::printf()
) はエクスポートされません。 これにより、<cstdio>
のような C ラッパー ヘッダーを含めると、stdio.h
などの C ヘッダー ファイルも含め、C グローバル名前空間のバージョンが取り込まれる状況が改善されます。 これはstd
をインポートしても問題ありません。std.compat
は、std
にあるすべてをエクスポートし、C ランタイムグローバル名前空間 (例:::printf
、::fopen
、::size_t
、::strlen
など) を追加します。 このstd.compat
モジュールを使用すると、グローバル名前空間の多くの C ランタイム関数/型を参照するコードベースを簡単に操作できます。
コンパイラは、import std;
または import std.compat;
を使用すると標準ライブラリ全体をインポートし、1 つのヘッダー ファイルを取り込むよりも高速になります。 たとえば、#include <vector>
するよりも、import std;
(または import std.compat
) を使用して標準ライブラリ全体を取り込む方が高速です。
名前付きモジュールではマクロが公開されないため、assert
、errno
、offsetof
、va_arg
などのマクロは、std
または std.compat
をインポートするときに使用できません。 回避策については「標準ライブラリの名前付きモジュールに関する考慮事項」を参照してください。
C++ モジュールについて
ヘッダー ファイルは、C++ のソース ファイル間で宣言と定義がどのように共有されているかを示します。 標準ライブラリ モジュールの前に、必要な標準ライブラリの一部を #include <vector>
ようなディレクティブと共に含めます。 ヘッダー ファイルを含める順序や、特定のマクロが定義されているかどうかによってセマンティクスが変わる可能性があるため、ヘッダー ファイルは脆弱であり、構成が困難です。 また、それらを含むすべてのソース ファイルによって再処理されるため、コンパイルが遅くなります。
C++20 では、 モジュールと呼ばれる最新の代替手段が導入されています。 C++23 では、モジュールのサポートを活用して、標準ライブラリを表す名前付きモジュールを導入することができました。
ヘッダー ファイルと同様に、モジュールを使うとソース ファイル間で宣言と定義を共有できます。 ただし、ヘッダー ファイルとは異なり、モジュールは脆弱ではなく、マクロ定義やインポート順序によってセマンティクスが変更されないため、作成が容易になります。 コンパイラは、#include
ファイルを処理するよりもはるかに高速にモジュールを処理でき、コンパイル時にも使用されるメモリが少なくなります。 名前付きモジュールでは、マクロ定義やプライベート実装の詳細は公開されません。
モジュールの詳細については、「C++ のモジュールの概要」を参照してください。その記事では、C++ 標準ライブラリをモジュールとして使用する方法についても説明しますが、以前の実験的な方法を使用しています。
この記事では、標準ライブラリを使用するための新しい最適な方法について説明しています。 標準ライブラリを使用する別の方法の詳細については、「ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーを比較する」を参照してください。
std
を使用して標準ライブラリをインポートする
次の例では、コマンド ライン コンパイラを使用して標準ライブラリをモジュールとして使用する方法を示します。 Visual Studio IDE でこれを行う方法については、「ビルド ISO C++23 標準ライブラリ モジュール」を参照してください。
ステートメント import std;
または import std.compat;
は標準ライブラリをアプリケーションにインポートします。 ただし、最初に、モジュールという名前の標準ライブラリをバイナリ形式にコンパイルする必要があります。 次の手順では、その方法を示します。
例: std
をビルドしてインポートする方法
Windows の [スタート] メニューから x86 Native Tools コマンド プロンプトを開き、「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
をコマンド ラインに追加します。
この例の主要なコマンド ライン スイッチは次のとおりです:
Switch 説明 /std:c++:latest
最新バージョンの C++ 言語標準およびライブラリを使用します。 モジュールのサポートは /std:c++20
で利用できますが、モジュールという名前の標準ライブラリのサポートを受けるためには、最新の標準ライブラリが必要です。/EHsc
extern "C"
としてマークされた関数を除き、C++ 例外処理を使用します。/W4
/W4 を使用することをお勧めします。特に新しいプロジェクトでは、すべてのレベル 1、レベル 2、レベル 3、およびほとんどのレベル 4 (情報) の警告が有効になるため、潜在的な問題を早期にキャッチするのに役立ちます。 基本的には、見つけにくいコードの欠陥を最も少なくするのに役立つ lint のような警告が提供されます。 /c
この時点でバイナリの名前付きモジュール インターフェイスを構築しているだけなので、リンクせずにコンパイルします。 次のスイッチを使用して、オブジェクト ファイル名と名前付きモジュール インターフェイス ファイル名を制御できます:
/Fo
はオブジェクト ファイルの名前を設定します。 たとえば、/Fo:"somethingelse"
のようにします。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx
) と同じ名前をオブジェクト ファイルに使用します。 この例では、モジュール ファイルstd.ixx
をコンパイルしているため、オブジェクト ファイル名は既定でstd.obj
になります。/ifcOutput
は、名前付きモジュール インターフェイス ファイル (.ifc
) の名前を設定します。 たとえば、/ifcOutput "somethingelse.ifc"
のようにします。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx
) と同じ名前をモジュール インターフェイス ファイル (.ifc
) に使用します。 この例では、モジュール ファイルstd.ixx
をコンパイルしているため生成されたifc
ファイルは既定でstd.ifc
になります。
作成した
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>
などの単一の標準ライブラリ ヘッダー ファイルを処理するよりもはるかに高速です。前の手順と同じディレクトリで次のコマンドを使用して、例をコンパイルします:
cl /c /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp link importExample.obj std.obj
コンパイラは、
import
ステートメントで指定されたモジュール名と一致する.ifc
ファイルを自動的に検索するため、この例のコマンド ラインで/reference "std=std.ifc"
を指定する必要はありません。 コンパイラが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"
を追加してアプリケーションをビルドする手順を組み合わせることができます。std
モジュールを使用する.cpp
ファイルの前に配置します。既定では、出力実行可能ファイルの名前は最初の入力ファイルから取得されます。
/Fe
コンパイラ オプションを使用して、必要な実行可能ファイル名を指定します。 このチュートリアルでは、std
名前付きモジュールを個別の手順としてコンパイルする方法について説明します。これは、モジュールという名前の標準ライブラリを 1 回ビルドするだけで済み、プロジェクトまたは複数のプロジェクトから参照できるためです。 ただし、次のコマンド ラインに示すように、すべてを一緒に構築すると便利な場合があります:cl /FeimportExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" importExample.cpp
前のコマンド ラインを指定すると、コンパイラは
importExample.exe
という名前の実行可能ファイルを生成します。 実行すると、生成される出力は次のとおりです:Import the STL library for best performance 555
std.compat
を使用して標準ライブラリとグローバル C 関数をインポートする
C++ 標準ライブラリには、ISO C 標準ライブラリが含まれています。 std.compat
モジュールは、std::vector
、std::cout
、std::printf
、std::scanf
など、std
モジュールのすべての機能を提供します。 ただし、これらの関数のグローバル名前空間バージョンも提供します (例: ::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.compat
が依存しているため、名前付きモジュール std
が最初にビルドされます:
Windows の [スタート] メニューから Native Tools コマンド プロンプトを開き、「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.compat
はstd
に基づいて構築されているため、コンパイラはimport std.compat;
を処理するstd.ifc
も参照します。 これはコンパイル時のみの成果物です。 アプリケーションに付属していません。std.obj
には標準ライブラリの実装が含まれています。std.compat.ifc
は、import std.compat;
ステートメントを処理するためにコンパイラが参照する名前付きモジュール インターフェイスのコンパイル済みバイナリです。 これはコンパイル時のみの成果物です。 アプリケーションに付属していません。std.compat.obj
には実装が含まれています。 ただし、ほとんどの実装はstd.obj
によって提供されます。 標準ライブラリから使用する機能をアプリケーションに静的にリンクするようにサンプル アプリをコンパイルするときに、std.obj
をコマンド ラインに追加します。
次のスイッチを使用して、オブジェクト ファイル名と名前付きモジュール インターフェイス ファイル名を制御できます:
/Fo
はオブジェクト ファイルの名前を設定します。 たとえば、/Fo:"somethingelse"
のようにします。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx
) と同じ名前をオブジェクト ファイルに使用します。 この例では、std.ixx
およびstd.compat.obj
モジュール ファイルをコンパイルするため、オブジェクト ファイル名は既定ではstd.obj
とstd.compat.obj
になります。/ifcOutput
は、名前付きモジュール インターフェイス ファイル (.ifc
) の名前を設定します。 たとえば、/ifcOutput "somethingelse.ifc"
のようにします。 既定では、コンパイラは、コンパイルするモジュール ソース ファイル (.ixx
) と同じ名前をモジュール インターフェイス ファイル (.ifc
) に使用します。 この例では、std.ixx
およびstd.compat.ixx
モジュール ファイルをコンパイルするため、生成されたifc
ファイルは既定ではstd.ifc
とstd.compat.ifc
になります。
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 <vector>
などの単一の#include
を処理するよりも高速です。次のコマンドを使用して、例をコンパイルします:
cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp link stdCompatExample.obj std.obj std.compat.obj
import
ステートメントでモジュール名と一致する.ifc
ファイルがコンパイラによって自動的に検索されるため、コマンド ラインでstd.compat.ifc
を指定する必要はありませんでした。 コンパイラがimport std.compat;
見つかると、ソース コードと同じディレクトリに配置されるため、std.compat.ifc
が見つかります。コマンド ラインで指定する必要がありません。.ifc
ファイルがソース コードとは異なるディレクトリにある場合、または名前が異なる場合は、/reference
コンパイラ スイッチを使用して参照します。std.compat
をインポートするときは、std.compat
とstd.obj
の両方に対してリンクする必要があります。std.compat
はstd.obj
でコードを使用するためです。1 つのプロジェクトをビルドする場合は、コマンド ラインに
"%VCToolsInstallDir%\modules\std.ixx"
と"%VCToolsInstallDir%\modules\std.compat.ixx"
を (その順序で) 追加することで、モジュールという名前のstd
とstd.compat
標準ライブラリをビルドする手順を組み合わせることができます。 このチュートリアルでは、モジュールという名前の標準ライブラリを 1 回だけビルドするだけで済み、プロジェクトまたは複数のプロジェクトから参照できるため、標準ライブラリ モジュールを別の手順としてビルドする方法について説明します。 ただし、すべてを一度にビルドするのが便利な場合は、それらを使用する.cpp
ファイルの前に配置し、次の例に示すようにビルドexe
に名前を付ける/Fe
を指定してください: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;
の場合は、NAN
や INFINITY
の代わりに numeric_limits<double>::quiet_NaN()
と numeric_limits<double>::infinity()
を使用し、INT_MIN
の代わりに std::numeric_limits<int>::min()
をすることができます。
まとめ
このチュートリアルでは、モジュールを使用して標準ライブラリをインポートしました。 次に、C++ の Named modules チュートリアルでは独自のモジュールを作成およびインポートする方法について説明します。
関連項目
ヘッダー ユニット、モジュール、プリコンパイル済みヘッダーを比較する
C++ のモジュールの概要
Visual Studio での C++ モジュールのツアー
C++ 名前付きモジュールにプロジェクトを移行する
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示