C++ のモジュールインターフェースパーティションの使いどころ(1)
C++ のモジュールの分割について
C++20 で導入されたモジュールでは、従来のヘッダファイルに代わってインターフェースファイルを使用します。インターフェースファイルではexport module 宣言により他のファイルでインポート可能なモジュール名を定義します。
インターフェースには基本となるプライマリーモジュールインターフェースのほかにインターフェースファイルを分割できるモジュールインターフェースパーティションがあります。
ひとつのモジュールを複数のファイルで構成する場合、二通りの方法が考えられます。一つは独立した各モジュールをexport import でインポートする方法。
export module module1;
export void func1();
export module module2;
export void func2();
export module mymodule;
export import module1;
export import module2;
もう一つはモジュールインターフェースパーティションを使用する方法。
export module mymomdule:module1;
export void func1();
export module mymomdule:module2;
export void func2();
export module mymodule;
export import :module1;
export import :module2;
どちらの場合もmymodule モジュールを利用するときには
import mymodule;
int main()
{
func1();
func2();
}
のように、mymodule をインポートすればmodule1, module2 でエクスポートされている関数を利用できます。
このように複数ファイルをまとめてひとつのモジュールにするのに二つのやり方がありますが、利用する側からはあまり差がないように見えます。モジュールを書く側からも違いはほぼ無いように感じられます。
ではどのように使い分けをすればよいのか、調べてみました。
モジュールインターフェースパーティションを使用してみる
まず最初に以下のような構成のプロジェクトで試します。なお処理の内容に意味はありません。モジュール関係の宣言に注目してください。環境はVisual Studio 2022 です。
すこしコードが続きますが、まずmylib というモジュールを作成しています。
mylib をアプリケーションの複数のファイルから利用しています。
各モジュールには以下のような依存関係があります。
mylib に含まれる機能を利用したい場合はimport mylib
とすれば良いため導入はシンプルになります。
しかしmylib に含まれるインターフェースファイルのどれかが変更された場合に、mylib に依存するものはすべてコンパイルが必要となります。たとえばother はmath には依存していませんが、mymath.ixx を変更するとother もコンパイル対象となります。
これは従来のヘッダファイルの場合でも同様の問題があり、#include を記述するときの手間を軽減するために色々とまとめたヘッダファイルを作ってしまうと一か所の変更により直接関係のない多数のソースファイルのコンパイルが必要となってしまいます。
モジュール導入の主目的はコンパイル時間の短縮であろうと思いますので、import も#include も必要なものだけを指定できるのが良さそうです。
結論
ところが、モジュールインターフェースパーティションを使用している場合にはモジュール外、つまり利用側からは特定のパーティションを指定してインポートすることはできません。例えば以下のような指定はできません。
import mylib:math; // NG
プライマリーモジュールインターフェースであればピンポイントでのインポートも可能で、まとめたければexport import でまとめることも可能なので基本的にはインターフェースファイルごとにモジュールを宣言する方が良さそうです。
なお、後からググってみたところclang ではデフォルトでモジュール名=インターフェースファイル名という想定のようです[1]。
Discussion