ブログ 約4分

C++モジュール機能がC++の未来にとってなぜそれほど重要なのか?

Share this article
C++モジュール機能がC++の未来にとってなぜそれほど重要なのか?

C++は長年停滞しており、多くの開発者は、この言語がCOBOL、Fortran、VB6と同じ運命をたどると確信していました。つまり、新規プロジェクトでは使われなくなり、C++開発者は既存プロジェクトを保守するだけになるという見方です。しかし、予想に反してC++は復活し、新しい標準によって言語の使われ方が大きく変わりました。

しかし、従来の #include メカニズムは今も残っています。言語のモダナイズが進んだ今、それは次に対処すべき弱い環となりました。実際、この仕組みには多くの欠点があります。その一部を、興味深い ドキュメント

  • コンパイル時間のスケーラビリティ:ヘッダーがインクルードされるたびに、コンパイラはそのヘッダーと、そこから推移的にインクルードされるすべてのヘッダーのテキストをプリプロセスして解析しなければなりません。この処理はアプリケーション内のすべての翻訳単位で繰り返されるため、膨大な冗長作業が発生します。 N 個の翻訳単位と、各翻訳単位でインクルードされる M 個のヘッダーがあるプロジェクトでは、コンパイラは M x N の作業を行います。これは、 M 個のヘッダーの大半が複数の翻訳単位で共有されているにもかかわらず発生します。テンプレートのコンパイルモデルによって大量のコードがヘッダーに置かれるため、C++は特に大きな影響を受けます。
  • 脆弱性#include ディレクティブは、プリプロセッサによってテキスト挿入として扱われます。そのため、インクルード時に有効なマクロ定義の影響を受けます。有効なマクロ定義がライブラリ内の名前と偶然衝突すると、ライブラリAPIを壊したり、ライブラリヘッダー自体でコンパイルエラーを引き起こしたりする可能性があります。極端な例として、 #define std "The C++ Standard" を定義してから標準ライブラリヘッダーをインクルードすると、C++標準ライブラリの実装で恐ろしい連鎖エラーが発生します。より現実的で微妙な問題としては、2つの異なるライブラリのヘッダーがマクロ衝突によって相互作用し、ユーザーが #include ディレクティブの順序を変更したり、 #undef ディレクティブを導入したりして、(意図しない)依存関係を断ち切らなければならないケースがあります。
  • 従来の回避策:Cプログラマーは、Cプリプロセッサモデルの脆弱性を回避するために多くの慣習を採用してきました。たとえば、大半のヘッダーでは、多重インクルードによってコンパイルが壊れないようにインクルードガードが必要です。マクロ名は衝突を避けるために LONG_PREFIXED_UPPERCASE_IDENTIFIERS のように書かれ、ライブラリやフレームワークの開発者の中には、ヘッダーで __underscored のような名前を使い、(慣例上)マクロになるべきではない「通常の」名前との衝突を避ける人さえいます。これらの慣習は、C以外の言語出身の開発者にとって参入障壁となり、経験豊富な開発者にとっては定型作業となり、ヘッダーを必要以上に見苦しくしています。
  • ツールの混乱:Cベースの言語では、ライブラリの境界が明確でないため、ソフトウェアライブラリとうまく連携するツールを構築するのが困難です。どのヘッダーが特定のライブラリに属しているのか、それらをどの順序でインクルードすれば正しくコンパイルできるのかが分かりません。ヘッダーはC、C++、Objective-C++、あるいはそれらの派生言語のどれなのでしょうか。ヘッダー内のどの宣言が実際にAPIの一部を意図したもので、どの宣言がヘッダーファイルとして記述する必要があったために存在しているだけなのでしょうか。

これらの問題に対する解決策は、すでに最初の C++0x草案 で導入されていました。それがモジュール機能です。しかし、新しい標準仕様が出るたびに導入は先送りされてきました。

モジュール:より堅牢な意味論モデル

モジュールは、より堅牢で効率的な意味論モデルによってライブラリへのアクセスを改善します。ユーザーの視点では、 import 宣言を #include プリプロセッサディレクティブの代わりに使うだけなので、コードの見た目はわずかに変わるだけです。C++0x草案の例を次に示します。

import std; // Module import directive.
int main() {
    std::cout << "Hello World\n";
}

複数のSTLファイルをインクルードする必要はありません。1回の import で十分であり、コードもよりクリーンになります。モジュールインポートは std モジュールのバイナリ表現を読み込み、そのAPIをアプリケーションから直接利用できるようにします。import 宣言の前にあるプリプロセッサ定義は、 stdが提供するAPIに影響しません。モジュール自体が、独立したスタンドアロンのモジュールとしてコンパイルされているためです。

VS2017でC++モジュールを開発する方法を紹介する興味深い記事があります。 Visual Studio 2017におけるC++モジュール

まとめ

C++モジュールは、include 機構に伴う問題を解消し、C#やJavaと同じように、ほかのライブラリをはるかに使いやすくします。

最初のモジュールを作成し、この強力な機能をぜひ試してみてください。

Share this article