C++ モジュールとは一体何でしょうか? 質問する

C++ モジュールとは一体何でしょうか? 質問する

私は C++ の標準化を追跡していて、C++ モジュールのアイデアに出会いました。それに関する良い記事を見つけることができませんでした。それは正確には何に関するものでしょうか?

ベストアンサー1

モチベーション

簡単に答えると、C++モジュールはヘッダそれはまた翻訳ユニット。これは、新しいコンテキストキーワードである とともに使用して、ライブラリの宣言にアクセスすることができるという点で、ヘッダーに似ています。importこれは翻訳単位(複雑なモジュールの場合は複数)であるため、コンパイルされます。別々にそして一度だけ。(#include文字通り内容をコピーする) この組み合わせにより、次のような多くの利点が得られます。

  1. 分離: モジュール ユニットは独立した翻訳単位であるため、usingインポートする翻訳単位や他のモジュールのマクロや宣言/ディレクティブに影響を与えず、またそれらから影響を受けることもない独自のマクロや宣言/ディレクティブのセットを持ちます。これにより、あるヘッダーの識別子#defineが別のヘッダーで使用されることによる衝突が防止されます。 の使用はusing慎重に行う必要がありますが、モジュール インターフェイスの名前空間スコープであっても、記述することは本質的に有害ではありませんusing namespace
  2. インターフェース制御: モジュール ユニットは、内部リンク (または を使用static) namespace {}export(C++98 以降、このような目的のために予約されているキーワード)、またはどちらも使用せずにエンティティを宣言できるため、クライアントが使用できるコンテンツの量を制限できます。これは、namespace detailヘッダー間で競合する可能性があるイディオム (同じ包含名前空間でそれを使用する) を置き換えます。
  3. 重複排除: 多くの場合、ヘッダー ファイルで宣言を提供し、別のソース ファイルで定義を提供する必要がなくなるため、冗長性とそれに伴う相違の機会が削減されます。
  4. 定義ルール違反の回避: ODRは、定義する特定のエンティティ(型、インライン関数/変数、テンプレート)を、それを使用するすべての翻訳単位で定義します。モジュールはエンティティを1回だけ定義して、それにもかかわらず、意味クライアントに。また、内部リンク宣言によってすでに ODR に違反している既存のヘッダーは、モジュールに変換されると不正な形式ではなくなり、診断は不要になります。
  5. 非ローカル変数の初期化順序import: (一意の)変数を含む翻訳単位間の依存関係を確立するため定義、明らかに順序がある静的ストレージ期間を持つ非ローカル変数を初期化するC++17 では、初期化順序を制御できる変数が提供されていましたが、モジュールではこれを通常の変数に拡張しています (変数はまったくinline必要ありません)。inline
  6. モジュールプライベート宣言: モジュール内で宣言され、エクスポートも内部リンクも持たないエンティティは、モジュール内の任意の翻訳単位で (名前で) 使用可能であり、既存の または の選択肢の間の便利な中間点を提供しますstatic。実装がこれらをどのように扱うかはまだわかりませんが、これらは動的オブジェクト内の「非表示の」(または「エクスポートされていない」) シンボルの概念に密接に対応しており、この実用的な動的リンク最適化を言語で認識できる可能性があります。
  7. ABI安定性: (ODR 互換性の目的がモジュール内で関連しない) のルールは、inline非インライン関数が共有ライブラリのアップグレードの ABI 境界として機能できる実装戦略をサポートするように調整されました (ただし、必須ではありません)。
  8. コンパイル速度: モジュールの内容は、それを使用するすべての翻訳単位の一部として再解析する必要がないため、多くの場合、コンパイルははるかに高速に進みます。モジュールは依存関係の順序で個別に処理する必要があるため、コンパイルのクリティカル パス (無限並列ビルドの待ち時間を制御する) は実際には長くなる可能性がありますが、合計 CPU 時間が大幅に短縮され、一部のモジュール/クライアントのみの再構築ははるかに高速になります。
  9. ツーリング: および を含む「構造宣言」にはimportmoduleプロジェクトの依存関係グラフを理解する必要のあるツールによって簡単かつ効率的に検出できるように、使用上の制限があります。この制限により、これらの一般的な単語の識別子としての既存の使用のほとんどすべてが許可されます。

アプローチ

モジュールで宣言された名前はクライアントで見つけられる必要があるため、重要な新しい種類の名前検索翻訳単位間で機能するルールが必要です。引数依存の参照とテンプレートのインスタンス化の正しいルールを取得することが、この提案が標準化されるまでに10年以上かかった大きな要因でした。単純なルールは、(明らかな理由により内部リンクと互換性がないことを除けばexportのみ名前検索。(例えば)decltypeまたはテンプレート パラメーターは、エクスポートされているかどうかに関係なく、まったく同じ動作をします。

モジュールは、クライアントが利用できるように型、インライン関数、テンプレートを提供する必要があるため、コンテンツ使用される場合、通常、コンパイラはモジュールを処理するときにアーティファクトを生成します(これはコンパイルされたモジュールインターフェース)には、クライアントが必要とする詳細な情報が含まれています。CMIは、プリコンパイル済みヘッダーただし、関連するすべての翻訳単位に同じヘッダーが同じ順序で含まれていなければならないという制約はありません。また、モジュールから特定の名前のみをインポートするという機能に類似するものはありませんが、Fortran モジュールの動作に似ています。

コンパイラは に基づいて CMI を見つけることができなければならないimport foo;(また に基づいてソース ファイルを見つけることがimport :partition;できなければならない) ため、“foo” から (CMI) ファイル名へのマッピングを知っている必要があります。Clang では、この概念を「モジュール マップ」という用語で表現しています。一般に、暗黙的なディレクトリ構造や、ソース ファイル名と一致しないモジュール (またはパーティション) 名などの状況をどのように処理するかはまだわかっていません。

非機能

他の「バイナリヘッダー」技術と同様に、モジュールは流通メカニズム(秘密主義の人は、ヘッダーと、含まれているテンプレートのすべての定義を提供することを避けたいと思うかもしれません)。また、コンパイラーがモジュールを使用して各プロジェクトの CMI を再生成することはできますが、従来の意味での「ヘッダーのみ」でもありません。

他の多くの言語では(例えば、Python)、モジュールはコンパイルだけでなく命名単位でもあるが、C++モジュールは名前空間ではないC++ にはすでに名前空間があり、モジュールによって使用法や動作が変わることはありません (一部は下位互換性のためです)。ただし、よく知られた名前空間名を持つライブラリの場合、特に他のモジュールの名前と紛らわしい名前空間名を持つ場合、モジュール名が名前空間名と一致することが予想されます。( はnested::nameモジュール名 としてレンダリングされる場合があります。これは、 が許可されていないnested.nameためです。 は、 C++20 では慣例を除き、意味を持ちません。).::.

モジュールは、pImpl イディオムまたは防止する脆弱な基本クラスの問題クライアントのクラスが完成している場合でも、そのクラスを変更するには、一般にクライアントを再コンパイルする必要があります。

最後に、モジュールは、マクロいくつかのライブラリのインターフェースの重要な部分である。次のようなラッパーヘッダーを提供することが可能である。

// wants_macros.hpp
import wants.macros;
#define INTERFACE_MACRO(x) (wants::f(x),wants::g(x))

#include(同じマクロの他の定義がない限り、ガードは必要ありません。)

マルチファイルモジュール

モジュールには1つのプライマリインターフェースユニットexport module A;: これは、クライアントが必要とするデータを生成するためにコンパイラによって処理される翻訳単位です。追加のインターフェースパーティションを含むexport module A:sub1;。これらは別々の翻訳単位であるが、モジュールの1つのCMIに含まれます。実装パーティション( module A:impl1;) は、モジュール全体のクライアントに内容を提供せずに、インターフェイスによってインポートできます。(一部の実装では、技術的な理由により、これらの内容がクライアントに漏洩する可能性がありますが、これは名前の検索には影響しません。)

最後に、(非パーティション)モジュール実装ユニット(単に を使用する場合module A;) はクライアントに何も提供しませんが、モジュール インターフェイスで宣言されたエンティティを定義できます (暗黙的にインポートされます)。モジュールのすべての翻訳単位は、内部リンケージがない限り (つまり を無視する限りexport)、インポートする同じモジュールの別の部分で宣言されたものを使用できます。

特別なケースとして、単一ファイルモジュールには、module :private;実装ユニットをインターフェースと効果的にパッケージ化する宣言を含めることができます。これはプライベートモジュールフラグメント特に、クラスを定義しながら、不完全なクライアント内 (バイナリ互換性は提供されますが、一般的なビルド ツールによる再コンパイルは妨げられません)。

アップグレード

ヘッダーベースのライブラリをモジュールに変換するのは、簡単な作業でも、大掛かりな作業でもありません。必要な定型文は非常に小さく (多くの場合 2 行)、export {}比較的大きなファイルのセクションを囲むことも可能です (ただし、残念ながら制限があります。static_assert宣言や推論ガイドを囲むことはできません)。一般に、はnamespace detail {}に変換するnamespace {}か、エクスポートしないままにしておくことができます。後者の場合、その内容は多くの場合、含まれている名前空間に移動されます。ABI に準拠した実装であってinlineも、他の翻訳単位からクラス メンバーへの呼び出しをインライン化する必要がある場合は、クラス メンバーを明示的にマークする必要があります。

もちろん、すべてのライブラリが即座にアップグレードできるわけではありません。後方互換性は常にC++の重点の1つであり、モジュールベースのライブラリをアップグレードできるようにする2つの別々のメカニズムがあります。依存するヘッダーベースのライブラリ (初期の実験的な実装によって提供されるものに基づく)。 (逆に言えば、ヘッダーは、importモジュールによってどちらの方法で使用されている場合でも、他のものと同じように使用できます。)

モジュール技術仕様書にあるように、グローバルモジュールフラグメントプリプロセッサ ディレクティブのみを含むモジュール ユニット (裸の で導入) の先頭に出現する場合がありますmodule;。具体的には、#includeモジュールが依存するヘッダーの です。ほとんどの場合、インクルードするヘッダーの宣言を使用するモジュールで定義されたテンプレートをインスタンス化することは可能です。これは、それらの宣言が CMI に組み込まれているためです。

「モジュラー」(またはインポート可能) ヘッダー ( import "foo.hpp";): インポートされるのは合成されたヘッダーユニットは、宣言しているすべてのものをエクスポートすること以外はモジュールのように動作します。内部リンク (ヘッダー外で使用すると (それでも!) ODR 違反が発生する可能性があります) やマクロもエクスポートします (インポートされたヘッダー ユニットごとに異なる値が指定されたマクロを使用するのはエラーです。コマンドライン マクロ ( -D) はこれに該当しません)。非公式には、特別なマクロを定義せずに 1 回インクルードするだけで十分な場合、ヘッダーはモジュール化されています (トークンを貼り付けるテンプレートの C 実装などではなく)。実装でヘッダーがインポート可能であることがわかっている場合は、#includeヘッダーの を自動的に に置き換えることができますimport

C++20 では、標準ライブラリは依然としてヘッダーとして提示されており、すべての C++ ヘッダー (C ヘッダーまたは<cmeow>ラッパーは除く) がインポート可能であると指定されています。C++23 では、おそらく名前付きモジュールも追加で提供される予定です (ただし、ヘッダーごとに 1 つではない可能性があります)。

非常にシンプルなモジュールは

export module simple;
import <string_view>;
import <memory>;
using std::unique_ptr;  // not exported
int *parse(std::string_view s) {/*…*/}  // cannot collide with other modules
export namespace simple {
  auto get_ints(const char *text)
  {return unique_ptr<int[]>(parse(text));}
}

これは、

import simple;
int main() {
  return simple::get_ints("1 1 2 3 5 8")[0]-1;
}

結論

モジュールはC++プログラミングをさまざまな方法で改善することが期待されていますが、改善は漸進的で(実際には)段階的です。委員会はモジュールを"新しい言語"例えば、これは符号付き整数と符号なし整数の比較のルールを変更します)、既存のコードの変換がより困難になり、モジュール ファイルと非モジュール ファイル間でコードを移動することが危険になるためです。

MSVCは、TSに忠実に従ったモジュールの実装をしばらく前から行っています。Clangも数年前からインポート可能なヘッダーの実装を行ってきました。GCCは、機能的ではありますが不完全な実装を持っています。標準化されたバージョン。

おすすめ記事