🍛

C++ のモジュールインターフェースパーティションの使いどころ(1)

2022/05/25に公開

C++ のモジュールの分割について

C++20 で導入されたモジュールでは、従来のヘッダファイルに代わってインターフェースファイルを使用します。インターフェースファイルではexport module 宣言により他のファイルでインポート可能なモジュール名を定義します。

インターフェースには基本となるプライマリーモジュールインターフェースのほかにインターフェースファイルを分割できるモジュールインターフェースパーティションがあります。

ひとつのモジュールを複数のファイルで構成する場合、二通りの方法が考えられます。一つは独立した各モジュールをexport import でインポートする方法。

module1.ixx
export module module1;

export void func1();
module2.ixx
export module module2;

export void func2();
mymodule.ixx
export module mymodule;
export import module1;
export import module2;

もう一つはモジュールインターフェースパーティションを使用する方法。

module1.ixx
export module mymomdule:module1;

export void func1();
module2.ixx
export module mymomdule:module2;

export void func2();
mymodule.ixx
export module mymodule;
export import :module1;
export import :module2;

どちらの場合もmymodule モジュールを利用するときには

main.cpp
import mymodule;

int main()
{
    func1();
    func2();
}

のように、mymodule をインポートすればmodule1, module2 でエクスポートされている関数を利用できます。

このように複数ファイルをまとめてひとつのモジュールにするのに二つのやり方がありますが、利用する側からはあまり差がないように見えます。モジュールを書く側からも違いはほぼ無いように感じられます。

ではどのように使い分けをすればよいのか、調べてみました。

モジュールインターフェースパーティションを使用してみる

まず最初に以下のような構成のプロジェクトで試します。なお処理の内容に意味はありません。モジュール関係の宣言に注目してください。環境はVisual Studio 2022 です。

すこしコードが続きますが、まずmylib というモジュールを作成しています。

https://github.com/uyamae/module_div/blob/master/app/mylib.ixx
https://github.com/uyamae/module_div/blob/master/app/mymath.ixx
https://github.com/uyamae/module_div/blob/master/app/myrandom.ixx

mylib をアプリケーションの複数のファイルから利用しています。

https://github.com/uyamae/module_div/blob/master/app/main.cpp
https://github.com/uyamae/module_div/blob/master/app/sub.ixx
https://github.com/uyamae/module_div/blob/master/app/sub.cpp
https://github.com/uyamae/module_div/blob/master/app/other.ixx
https://github.com/uyamae/module_div/blob/master/app/other.cpp

各モジュールには以下のような依存関係があります。

mylib に含まれる機能を利用したい場合はimport mylib とすれば良いため導入はシンプルになります。

しかしmylib に含まれるインターフェースファイルのどれかが変更された場合に、mylib に依存するものはすべてコンパイルが必要となります。たとえばother はmath には依存していませんが、mymath.ixx を変更するとother もコンパイル対象となります。

これは従来のヘッダファイルの場合でも同様の問題があり、#include を記述するときの手間を軽減するために色々とまとめたヘッダファイルを作ってしまうと一か所の変更により直接関係のない多数のソースファイルのコンパイルが必要となってしまいます。

モジュール導入の主目的はコンパイル時間の短縮であろうと思いますので、import も#include も必要なものだけを指定できるのが良さそうです。

結論

ところが、モジュールインターフェースパーティションを使用している場合にはモジュール外、つまり利用側からは特定のパーティションを指定してインポートすることはできません。例えば以下のような指定はできません。

main.cpp
import mylib:math; // NG

プライマリーモジュールインターフェースであればピンポイントでのインポートも可能で、まとめたければexport import でまとめることも可能なので基本的にはインターフェースファイルごとにモジュールを宣言する方が良さそうです。

なお、後からググってみたところclang ではデフォルトでモジュール名=インターフェースファイル名という想定のようです[1]

脚注
  1. https://runom.hatenablog.com/entry/2019/04/08/125130 ↩︎

Discussion