C++20 モジュール:プライベート モジュール フラグメントとヘッダー ユニット

ここ数週間。 C++20 のモジュールについて、プライベート モジュール フラグメントとヘッダー ユニットという新しいことを学びました。そのため、この投稿では少し回り道をして、これらの新機能を紹介します。

可変個引数テンプレートに関する約束した投稿をなぜ完了しないのか不思議に思うかもしれません。理由は簡単です。来週公開する次の pdf バンドルは C++20 モジュールに関するもので、この投稿をこのバンドルに組み込みたいと考えています。その前に、この投稿を書かなければなりません。

プライベート モジュール フラグメントとヘッダー ユニットにより、C++20 でのモジュールの扱いがより快適になります。

この投稿では、最新の Visual Studio コンパイラを意図的に使用しています。その C++20 モジュールのサポートはほぼ完全であるためです。最新の GCC と Clang はモジュールを部分的にしかサポートしていません。

private モジュールフラグメント

モジュール インターフェイス ユニットとモジュール実装ユニットについての事実が準備できているかどうかわかりません。したがって、重要な事実を繰り返します。

モジュールをインターフェイスと実装に分離する場合は、モジュール インターフェイス ユニットと 1 つ以上のモジュール実装ユニットに構造化する必要があります。

モジュール インターフェース ユニット

// mathInterfaceUnit2.ixx

module; 

#include <vector> 

export module math; 

export namespace math {

 int add(int fir, int sec);
 
 int getProduct(const std::vector<int>& vec);

}

  • モジュール インターフェイス ユニットには、エクスポート モジュール宣言が含まれています:export module math.
  • add と getProduct という名前がエクスポートされます。
  • モジュールは、モジュール インターフェース ユニットを 1 つだけ持つことができます。

モジュール実装ユニット

// mathImplementationUnit2.cpp

module math;

#include <numeric>

namespace math {

 int add(int fir, int sec){
 return fir + sec;
 }

 int getProduct(const std::vector<int>& vec) {
 return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
 }
}

  • モジュール実装ユニットには、非エクスポート モジュール宣言が含まれています:module math;
  • モジュールには、複数のモジュール実装ユニットを含めることができます。

メイン プログラム

// client4.cpp

#include <iostream>
#include <vector> import math; int main() { std::cout << std::endl; std::cout << "math::add(2000, 20): " << math::add(2000, 20) << std::endl; std::vector<int> myVec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; std::cout << "math::getProduct(myVec): " << math::getProduct(myVec) << std::endl; std::cout << std::endl; }

  • ユーザーの観点からは、名前空間の計算のみが追加されました。

実行可能ファイルのビルド

実行可能ファイルを手動でビルドするには、いくつかの手順が含まれます。

cl.exe /std:c++latest /c mathInterfaceUnit2.ixx /EHsc // (1)
cl.exe /std:c++latest /c mathImplementationUnit2.cpp /EHsc // (2)
cl.exe /std:c++latest /c client4.cpp /EHsc // (3)
cl.exe client4.obj mathInterfaceUnit2.obj mathImplementationUnit2.obj // (4)

<オール>
  • オブジェクト ファイル mathInterfaceUnit2.obj とモジュール インターフェイス ファイル math.ifc を作成します。
  • オブジェクト ファイル mathImplementationUnit2.obj を作成します。
  • オブジェクト ファイル client4.obj を作成します。
  • 実行可能な client4.exe を作成します。
  • Microsoft コンパイラの場合、例外処理モデル (/EHsc) を指定する必要があります。さらに、フラグ /std:c++latest を使用してください。

    最後に、プログラムの出力は次のとおりです。

    モジュールをモジュール インターフェース ユニットと 1 つ以上のモジュール実装ユニットに構造化する大きな利点の 1 つは、モジュール実装ユニットの変更がモジュール インターフェース ユニットに影響を与えないため、再コンパイルが必要ないことです。

    Private モジュールフラグメント

    プライベート モジュール フラグメントのおかげで、モジュールを 1 つのファイルに実装し、その最後の部分を module :private; を使用してその実装として宣言できます。 .したがって、プライベート モジュール フラグメントを変更しても、再コンパイルは発生しません。次のモジュール宣言ファイル mathInterfaceUnit3.ixx モジュール インターフェイス ユニット mathInterfaceUnit2.ixx をリファクタリングします およびモジュール実装ユニット mathImplementationUnit2.cpp

    // mathInterfaceUnit3.ixx
    
    module; 
    
    #include <numeric>
    #include <vector>
    
    export module math; 
    
    export namespace math {
    
     int add(int fir, int sec);
    
     int getProduct(const std::vector<int>& vec);
    
    }
    
    module :private; // (1)
    
    int add(int fir, int sec) {
     return fir + sec;
    }
    
    int getProduct(const std::vector<int>& vec) {
     return std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<int>());
    }
    

    module: private; (1 行目) は private の開始を示します モジュールフラグメント。モジュール宣言ファイルのこのオプションの最後の部分を変更しても、再コンパイルは発生しません。

    以前の投稿でヘッダー ユニットを紹介しました。今、私はそれらを使用することができます

    ヘッダー ユニット

    ヘッダー ユニットは、ヘッダーからモジュールへのスムーズな移行方法です。 #include を置き換える必要があります 新しい import を使用したディレクティブ

    #include <vector> => import <vector>;
    #include "myHeader.h" => import "myHeader.h"; 
    

    まず、インポートはインクルードと同じルックアップ ルールに従います。これは、引用符 ("myHeader.h") の場合、ルックアップがシステム検索パスを続行する前に、最初にローカル ディレクトリを検索することを意味します。

    第二に、これは単なるテキスト置換ではありません。この場合、コンパイラはインポート ディレクティブからモジュールのようなものを生成し、その結果をモジュールであるかのように扱います。 importing module ステートメントは、ヘッダーのすべてのエクスポート可能な名前を取得します。エクスポート可能な名前にはマクロが含まれます。これらの合成済みヘッダー ユニットのインポートは高速で、プリコンパイル済みヘッダーに匹敵する速度です。

    モジュールはプリコンパイル済みヘッダーではありません

    プリコンパイル済みヘッダーは、コンパイラーの処理がより高速な中間形式でヘッダーをコンパイルする標準化されていない方法です。 Microsoft コンパイラは、拡張子 .pch を使用します と GCC コンパイラ .gch プリコンパイル済みヘッダー用。プリコンパイル済みヘッダーとモジュールの主な違いは、モジュールが名前を選択的にエクスポートできることです。モジュール内でのみ、エクスポートされた名前はモジュールの外で見ることができます。

    この短い残りの後、試してみましょう。

    ヘッダー ユニットの使用

    次の例は、3 つのファイルで構成されています。ヘッダファイル head.h 、関数 hello を宣言 、その実装ファイル head.cpp 、関数 hello を定義します 、およびクライアント ファイル helloWorld3.cpp 関数 hello を使用する .

    // head.h
    
    #include <iostream>
    
    void hello();
    

    実装ファイル head.cpp のみ クライアントファイル helloWorld3.cpp 特別です。ヘッダー ファイル head.h: import "head.h";. をインポートします。

    // head.cpp
    
    import "head.h";
    
    void hello() {
    
     std::cout << '\n';
    
     std::cout << "Hello World: header units\n";
    
     std::cout << '\n';
    
    }
    

    // helloWorld3.cpp

    import "head.h"; int main() { hello(); }

    これらは、ヘッダー ユニットを使用するために必要な手順です。

    cl.exe /std:c++latest /EHsc /exportHeader head.h 
    cl.exe /c /std:c++latest /EHsc /headerUnit head.h=head.h.ifc head.cpp
    cl.exe /std:c++latest /EHsc /headerUnit head.h=head.h.ifc helloWorld3.cpp head.obj 
    

    • フラグ /exportHeader (最初の行) ifc ファイルを作成します head.h.ifc ヘッダファイル head.hから . ifc ファイルには、モジュール インターフェースのメタデータ記述が含まれています。
    • 実装ファイル head.cpp (2 行目) とクライアント ファイル helloWordl3.cpp (3 行目) ヘッダー ユニットを使用します。フラグ /headerUnit head.h=head.h.ifc ヘッダーをインポートし、指定されたヘッダーの ifc ファイルの名前をコンパイラまたはリンカーに伝えます。

    ヘッダー ユニットには 1 つの欠点があります。すべてのヘッダーがインポートできるわけではありません。インポート可能なヘッダーは実装定義ですが、C++ 標準では、すべての標準ライブラリ ヘッダーがインポート可能なヘッダーであることが保証されています。インポートする機能は、C ヘッダーを除外します。

    次は?

    次の投稿では、可変個引数テンプレートを使用して、完全に汎用的なファクトリの C++ イディオムを実装します。この命を救う C++ イディオムの実装の 1 つは、 std::make_unique です。 .