モジュール (C++20 から)
ほとんどの C++ プロジェクトは複数の翻訳単位を使用するため、それらの単位間で宣言や定義を共有する必要があります。この目的でヘッダーの使用が主流であり、その一例が標準ライブラリです。その宣言は対応するヘッダーをインクルードすることで提供されます。
モジュールは、翻訳単位間で宣言と定義を共有するための言語機能です。これは、ヘッダーのいくつかのユースケースに対する代替手段となります。
モジュールは名前空間とは直交する機能です。
// helloworld.cpp export module helloworld; // module declaration import <iostream>; // import declaration export void hello() // export declaration { std::cout << "Hello world!\n"; }
// main.cpp import helloworld; // import declaration int main() { hello(); }
目次 |
[編集] 構文
export(任意) module モジュール名 モジュールパーティション(任意) 属性(任意) ; |
(1) | ||||||||
export 宣言 |
(2) | ||||||||
export { 宣言シーケンス(任意) } |
(3) | ||||||||
export(任意) import モジュール名 属性(任意) ; |
(4) | ||||||||
export(任意) import モジュールパーティション 属性(任意) ; |
(5) | ||||||||
export(任意) import ヘッダー名 属性(任意) ; |
(6) | ||||||||
module;
|
(7) | ||||||||
module : private;
|
(8) | ||||||||
[編集] モジュール宣言
翻訳単位はモジュール宣言を持つことができ、その場合、その翻訳単位はモジュールユニットと見なされます。モジュール宣言は、もし提供されるなら、その翻訳単位の最初の宣言でなければなりません(後述のグローバルモジュールフラグメントは例外)。各モジュールユニットは、モジュール宣言で提供されるモジュール名(および任意でパーティション)に関連付けられます。
export(任意) module モジュール名 モジュールパーティション(任意) 属性(任意) ; |
|||||||||
モジュール名は、ドットで区切られた1つ以上の識別子で構成されます(例:mymodule、mymodule.mysubmodule、mymodule2...)。ドットには本質的な意味はありませんが、非公式に階層構造を表すために使用されます。
モジュール名またはモジュールパーティション内のいずれかの識別子がオブジェクト形式マクロとして定義されている場合、プログラムは不適格となります。
名前付きモジュールとは、同じモジュール名を持つモジュールユニットの集合です。
宣言に export キーワードを持つモジュールユニットはモジュールインターフェースユニットと呼ばれ、それ以外のすべてのモジュールユニットはモジュール実装ユニットと呼ばれます。
すべての名前付きモジュールには、モジュールパーティションを指定しないモジュールインターフェースユニットが正確に1つだけ存在しなければなりません。このモジュールユニットはプライマリモジュールインターフェースユニットと呼ばれます。そのエクスポートされた内容は、対応する名前付きモジュールをインポートする際に利用可能になります。
// (each line represents a separate translation unit) export module A; // declares the primary module interface unit for named module 'A' module A; // declares a module implementation unit for named module 'A' module A; // declares another module implementation unit for named module 'A' export module A.B; // declares the primary module interface unit for named module 'A.B' module A.B; // declares a module implementation unit for named module 'A.B'
[編集] 宣言と定義のエクスポート
モジュールインターフェースユニットは宣言(定義を含む)をエクスポートでき、それは他の翻訳単位によってインポートできます。宣言をエクスポートするには、宣言の前に export キーワードを付けるか、export ブロック内に配置します。
export 宣言 |
|||||||||
export { 宣言シーケンス(任意) } |
|||||||||
export module A; // declares the primary module interface unit for named module 'A' // hello() will be visible by translations units importing 'A' export char const* hello() { return "hello"; } // world() will NOT be visible. char const* world() { return "world"; } // Both one() and zero() will be visible. export { int one() { return 1; } int zero() { return 0; } } // Exporting namespaces also works: hi::english() and hi::french() will be visible. export namespace hi { char const* english() { return "Hi!"; } char const* french() { return "Salut!"; } }
[編集] モジュールとヘッダーユニットのインポート
モジュールはインポート宣言を介してインポートされます。
export(任意) import モジュール名 属性(任意) ; |
|||||||||
指定された名前付きモジュールのモジュールインターフェースユニットでエクスポートされたすべての宣言と定義は、インポート宣言を使用した翻訳単位で利用可能になります。
インポート宣言はモジュールインターフェースユニットでエクスポートできます。つまり、モジュール B がモジュール A をエクスポートインポートする場合、B をインポートすると A からのエクスポートもすべて可視になります。
モジュールユニット内では、すべてのインポート宣言(エクスポートインポートを含む)は、モジュール宣言の後、かつ他のすべての宣言の前にグループ化されなければなりません。
/////// A.cpp (primary module interface unit of 'A') export module A; export char const* hello() { return "hello"; } /////// B.cpp (primary module interface unit of 'B') export module B; export import A; export char const* world() { return "world"; } /////// main.cpp (not a module unit) #include <iostream> import B; int main() { std::cout << hello() << ' ' << world() << '\n'; }
#include はモジュールユニット内で(グローバルモジュールフラグメントの外では)使用すべきではありません。なぜなら、インクルードされたすべての宣言と定義がモジュールの一部と見なされるからです。代わりに、ヘッダーはインポート宣言を用いてヘッダーユニットとしてインポートすることもできます。
export(任意) import ヘッダー名 属性(任意) ; |
|||||||||
ヘッダーユニットは、ヘッダーから合成される独立した翻訳単位です。ヘッダーユニットをインポートすると、そのすべての定義と宣言にアクセスできるようになります。プリプロセッサマクロも(インポート宣言がプリプロセッサによって認識されるため)アクセス可能です。
しかし、#include とは異なり、インポート宣言の時点ですでに定義されているプリプロセッサマクロは、ヘッダーの処理に影響を与えません。これは一部のケース(一部のヘッダーはプリプロセッサマクロを一種の設定として使用する)では不便な場合があり、その場合はグローバルモジュールフラグメントの使用が必要になります。
/////// A.cpp (primary module interface unit of 'A') export module A; import <iostream>; export import <string_view>; export void print(std::string_view message) { std::cout << message << std::endl; } /////// main.cpp (not a module unit) import A; int main() { std::string_view message = "Hello, world!"; print(message); }
[編集] グローバルモジュールフラグメント
モジュールユニットはグローバルモジュールフラグメントを先頭に置くことができます。これは、ヘッダーのインポートが不可能な場合(特にヘッダーが設定としてプリプロセッサマクロを使用する場合)にヘッダーをインクルードするために使用できます。
module;
プリプロセッシングディレクティブ(任意) モジュール宣言 |
|||||||||
モジュールユニットがグローバルモジュールフラグメントを持つ場合、その最初の宣言は module; でなければなりません。その後、グローバルモジュールフラグメントにはプリプロセッシングディレクティブのみが出現できます。そして、通常のモジュール宣言がグローバルモジュールフラグメントの終わりとモジュール内容の始まりを示します。
/////// A.cpp (primary module interface unit of 'A') module; // Defining _POSIX_C_SOURCE adds functions to standard headers, // according to the POSIX standard. #define _POSIX_C_SOURCE 200809L #include <stdlib.h> export module A; import <ctime>; // Only for demonstration (bad source of randomness). // Use C++ <random> instead. export double weak_random() { std::timespec ts; std::timespec_get(&ts, TIME_UTC); // from <ctime> // Provided in <stdlib.h> according to the POSIX standard. srand48(ts.tv_nsec); // drand48() returns a random number between 0 and 1. return drand48(); } /////// main.cpp (not a module unit) import <iostream>; import A; int main() { std::cout << "Random value between 0 and 1: " << weak_random() << '\n'; }
[編集] プライベートモジュールフラグメント
プライマリモジュールインターフェースユニットは、プライベートモジュールフラグメントを末尾に置くことができます。これにより、モジュールの内容のすべてをインポート元から到達可能にすることなく、モジュールを単一の翻訳単位として表現できます。
module : private;
宣言シーケンス(任意) |
|||||||||
プライベートモジュールフラグメントは、他の翻訳単位の振る舞いに影響を与える可能性のあるモジュールインターフェースユニットの部分を終了させます。モジュールユニットがプライベートモジュールフラグメントを含む場合、それはそのモジュールの唯一のモジュールユニットとなります。
export module foo; export int f(); module : private; // ends the portion of the module interface unit that // can affect the behavior of other translation units // starts a private module fragment int f() // definition not reachable from importers of foo { return 42; }
[編集] モジュールパーティション
モジュールはモジュールパーティションユニットを持つことができます。これらは、モジュール宣言にモジュールパーティションを含むモジュールユニットであり、モジュールパーティションはコロン : で始まり、モジュール名の後に置かれます。
export module A:B; // Declares a module interface unit for module 'A', partition ':B'.
モジュールパーティションは正確に1つのモジュールユニットを表します(2つのモジュールユニットが同じモジュールパーティションを指定することはできません)。それらは名前付きモジュールの内部からのみ可視です(名前付きモジュールの外部の翻訳単位はモジュールパーティションを直接インポートできません)。
モジュールパーティションは、同じ名前付きモジュールのモジュールユニットによってインポートできます。
export(任意) import モジュールパーティション 属性(任意) ; |
|||||||||
/////// A-B.cpp export module A:B; ... /////// A-C.cpp module A:C; ... /////// A.cpp export module A; import :C; export import :B; ...
モジュールパーティション内のすべての定義と宣言は、エクスポートされているかどうかに関わらず、インポート元のモジュールユニットから可視になります。
モジュールパーティションは(そのモジュール宣言が export を持つ場合)モジュールインターフェースユニットになることができます。それらはプライマリモジュールインターフェースユニットによってエクスポートインポートされなければならず、そのエクスポートされた文はモジュールがインポートされるときに可視になります。
export(任意) import モジュールパーティション 属性(任意) ; |
|||||||||
/////// A.cpp export module A; // primary module interface unit export import :B; // Hello() is visible when importing 'A'. import :C; // WorldImpl() is now visible only for 'A.cpp'. // export import :C; // ERROR: Cannot export a module implementation unit. // World() is visible by any translation unit importing 'A'. export char const* World() { return WorldImpl(); }
/////// A-B.cpp export module A:B; // partition module interface unit // Hello() is visible by any translation unit importing 'A'. export char const* Hello() { return "Hello"; }
/////// A-C.cpp module A:C; // partition module implementation unit // WorldImpl() is visible by any module unit of 'A' importing ':C'. char const* WorldImpl() { return "World"; }
/////// main.cpp import A; import <iostream>; int main() { std::cout << Hello() << ' ' << World() << '\n'; // WorldImpl(); // ERROR: WorldImpl() is not visible. }
[編集] モジュールの所有権
一般に、宣言がモジュールユニット内のモジュール宣言の後に現れる場合、それはそのモジュールにアタッチされます。
エンティティの宣言が名前付きモジュールにアタッチされている場合、そのエンティティはそのモジュール内でのみ定義できます。そのようなエンティティのすべての宣言は、同じモジュールにアタッチされなければなりません。
宣言が名前付きモジュールにアタッチされており、かつエクスポートされていない場合、宣言された名前はモジュールリンケージを持ちます。
export module lib_A; int f() { return 0; } // f has module linkage export int x = f(); // x equals 0
export module lib_B; int f() { return 1; } // OK, f in lib_A and f in lib_B refer to different entities export int y = f(); // y equals 1
エンティティの2つの宣言が異なるモジュールにアタッチされている場合、プログラムは不適格となります。どちらも他方から到達可能でない場合、診断は要求されません。
/////// decls.h int f(); // #1, attached to the global module int g(); // #2, attached to the global module
/////// Module interface of M module; #include "decls.h" export module M; export using ::f; // OK, does not declare an entity, exports #1 int g(); // Error: matches #2, but attached to M export int h(); // #3 export int k(); // #4
/////// Other translation unit import M; static int h(); // Error: matches #3 int k(); // Error: matches #4
以下の宣言は、どの名前付きモジュールにもアタッチされません(したがって、宣言されたエンティティはモジュールの外部で定義できます)。
export module lib_A; namespace ns // ns is not attached to lib_A. { export extern "C++" int f(); // f is not attached to lib_A. extern "C++" int g(); // g is not attached to lib_A. export int h(); // h is attached to lib_A. } // ns::h must be defined in lib_A, but ns::f and ns::g can be defined elsewhere (e.g. // in a traditional source file).
[編集] ノート
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_modules |
201907L |
(C++20) | モジュール — コア言語サポート |
__cpp_lib_modules |
202207L |
(C++23) | 標準ライブラリモジュール std および std.compat |
[編集] キーワード
private, module, import, export
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 2732 | C++20 | インポート可能なヘッダーがインポート元のプリプロセッサの状態に 反応できるかどうかが不明確だった |
反応しない |
| P3034R1 | C++20 | モジュール名とモジュールパーティションが オブジェクト形式マクロとして定義された識別子を含めることができた |
禁止された |