モジュールの詳細

前回の投稿では、C++20 のモジュールについて紹介しました。この投稿では、既存のモジュールの使用方法を示します。

この投稿を始める前に、モジュールへの最初の投稿で終わったことを簡単にまとめさせてください。

短い要約

モジュール インターフェイス ユニットとモジュール実装ユニット、およびそれを使用するクライアントから構成されるモジュール math1 を作成しました。これが 3 つのソース ファイルです。

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

// math1.cppm

export module math1;

export int add(int fir, int sec);

モジュール実装ユニット

// math1.cpp

module math1;

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

クライアント

// main1.cpp

import math1;

int main(){
 
 add(2000, 20);
 
}

現在のclangおよびcl.exeコンパイラでプログラムをコンパイルしました。コンパイル行が少し短いため、今後は cl.exe コンパイラを使用します。前回の投稿で約束したように、プログラムの出力をお見せしましょう。

標準モジュールの使用

モジュール math2 では、モジュール インターフェイス ユニットもモジュール実装ユニットも変更されていません。

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

// math2.cppm

export module math2;

export int add(int fir, int sec);

モジュール実装ユニット

// math2.cpp

module math2;

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

クライアント

// main2.cpp

//#include <iostream>

import std.core;

import math2;

int main(){
 
 std::cout << std::endl;
 
 std::cout << "add(2000, 20): " << add(2000, 20) << std::endl;
 
}

モジュール std.core のおかげで、追加の結果を表示できます。

ヘッダー を使用することも可能です。もちろん、どのモジュールが利用可能かという質問を聞きます。これは、Microsoft C++ チーム ブログの記事「Using C++ Modules in Visual Studio 2017」からの抜粋です。

Visual Studio 2017 の C++ モジュール

  • std.regex ヘッダー <regex> の内容を提供します
  • std.filesystem ヘッダー <experimental/filesystem> の内容を提供します
  • std.memory ヘッダー <memory> の内容を提供します
  • std.threading ヘッダー <atomic> の内容を提供します 、 <condition_variable><future><mutex><shared_mutex><thread>
  • std.core C++ 標準ライブラリの他のすべてを提供します

モジュールは、ヘッダーよりも高度な抽象化を提供します。これにより、それらを使用するのが非常に快適になります。さらに、モジュールのどの名前をエクスポートするかどうかを指定できます。

輸出と非輸出

次のモジュール math3 は、前のものより少し複雑です。これがインターフェースです。

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

// math3.cppm

import std.core;

export module math3;

int add(int fir, int sec);

export int mult(int fir, int sec);

export void doTheMath();

モジュール インターフェイス ユニットには、エクスポート モジュール宣言、export module math3; が含まれています。モジュール宣言は、いわゆるモジュール範囲を開始します . export で宣言された、モジュールの範囲の後の名前のみがエクスポートされます。そうでない場合、名前はモジュールの外では見えないため、モジュールのリンケージがあります。これは特に関数 add に当てはまりますが、関数 mult と doTheMath には当てはまりません。

モジュール実装ユニット

// math3.cpp

module math3;

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

int mult(int fir, int sec){
 return fir * sec;
}

void doTheMath(){
 std::cout << "add(2000, 20): " << add(2000, 20) << std::endl;
}

モジュール実装ユニットに追加するものは何もありません。メインプログラムはもっと面白いです。

クライアント

// main3.cpp

// #include <iostream> // (1)
// #include <numeric> // (1)
// #include <string> // (1)
// #include <vector> // (1)
import std.core; // (2)

import math3;

int main(){
 
 std::cout << std::endl;
 
 // std::cout << "add(2000, 20): " << add(2000, 20) << std::endl; // (3)
 
 std::vector<int> myVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 
 std::string doc = "std::accumulate(myVec.begin(), myVec.end(), mult): "; 
 auto prod = std::accumulate(myVec.begin(), myVec.end(), 1, mult);
 
 std::cout << doc << prod << std::endl; 
 
 doTheMath();
 
}

ご覧のとおり、私の場合、モジュールは非常に快適です。行 (1) で 4 つのヘッダーを使用する代わりに、行 (2) の単純な import std.core で問題ありません。それだけでした。これがプログラムの出力です。

さて、質問に:行 (3) で関数 add を使用するとどうなりますか。要約すると、add はエクスポートされないため、モジュール リンケージがあります。

コンパイラは、関数 add がメイン プログラムで使用されていると報告しますが、add という名前は表示されません。

詳細

まず、さまざまな方法でエクスポートできます。

エクスポート

math3.cppm などのエクスポート指定子を使用して名前をエクスポートするのは面倒です。

エクスポート指定子

// math3.cppm

import std.core;

export module math3;

int add(int fir, int sec);

export int mult(int fir, int sec);

export void doTheMath()
エクスポート指定子の代わりに、エクスポートされたグループを使用できます。

エクスポートされたグループ

// math3.cppm

import std.core;

export module math3;

int add(int fir, int sec);

export {

int mult(int fir, int sec); void doTheMath();

}
3 番目のバリエーションは、エクスポートされた名前空間を使用することです。

エクスポートされた名前空間

// math3.cppm

import std.core;

export module math3;

namespace math3 {

int add(int fir, int sec);

}

export namespace math3 {

int mult(int fir, int sec); void doTheMath();

}
3 つのバリエーションはすべて意味的に同等です。

モジュールを再エクスポートするのも非常に快適かもしれません

モジュールの再エクスポート

別のモジュールからインポートしたものをエクスポートしたい場合があります。インポートされたモジュールをエクスポートしない場合、インポートされたモジュールにはモジュール リンケージがあり、その名前はモジュールの外では見えません。以下は具体的な例です。

見えるものと見えないもの

モジュール math.core と math.core2 を新しいモジュール math にインポートして使用したいとします。ここにmath.coreとmath.core2のモジュールインターフェースユニットがあります。

  • 再エクスポートされたモジュール

// module interface unit of math.core

export math.core

export int mult(int fir, int sec); 

// module interface unit of math.core2

export math.core2

export int add(int fir, int sec); 

次に、これが新しいモジュールの数学です。

  • 新しいモジュールの数学

// module interface unit of math

export module math;

import math.core; // not exported with mult
export import math.core2; // exported with add


// module implementation unit of math

mult(1100, 2); // fine
add(2000, 20); // fine

ご覧のとおり、モジュール math でエクスポートされた名前とエクスポートされていない名前を使用してもまったく問題ありません。ただし、モジュール math.core はエクスポートされません。モジュール math を使用するクライアントのみが違いを確認できます。

  • クライアント
// Client

import math

mult(1100, 2); // ERROR
add(2000, 20); // fine

関数 mult にはモジュール リンケージがあるため、モジュールの外からは見えません。関数 add のみが表示されます。

モジュールの再パッケージ化

モジュールを再パッケージ化する快適な方法があります。エクスポートされたグループに入れるだけです。

export module math;

export{

 import math.core;
 import math.core2;
 import math.basics;
 
}

これにより、モジュール math をインポートするクライアントのすべての名前が表示されます。

次は?

次回の記事では、C++ コア ガイドラインの最後のメイン トピックである、標準ライブラリの規則について説明します。信じられないかもしれませんが、プロの C++ 開発者の多くは標準テンプレート ライブラリ (STL) を使用していません。これは、特に STL のアルゴリズムに当てはまります。