名前空間
変種
操作

モジュール (C++20 から)

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

ほとんどの 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)
1) モジュール宣言。現在の翻訳単位がモジュールユニットであることを宣言します。
2,3) エクスポート宣言。宣言または宣言シーケンス内のすべての名前空間スコープの宣言をエクスポートします。
4,5,6) インポート宣言。モジュールユニット/モジュールパーティション/ヘッダーユニットをインポートします。

[編集] モジュール宣言

翻訳単位はモジュール宣言を持つことができ、その場合、その翻訳単位はモジュールユニットと見なされます。モジュール宣言は、もし提供されるなら、その翻訳単位の最初の宣言でなければなりません(後述のグローバルモジュールフラグメントは例外)。各モジュールユニットは、モジュール宣言で提供されるモジュール名(および任意でパーティション)に関連付けられます。

export(任意) module モジュール名 モジュールパーティション(任意) 属性(任意) ;

モジュール名は、ドットで区切られた1つ以上の識別子で構成されます(例:mymodulemymodule.mysubmodulemymodule2...)。ドットには本質的な意味はありませんが、非公式に階層構造を表すために使用されます。

モジュール名またはモジュールパーティション内のいずれかの識別子がオブジェクト形式マクロとして定義されている場合、プログラムは不適格となります。

名前付きモジュールとは、同じモジュール名を持つモジュールユニットの集合です。

宣言に 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 モジュール名とモジュールパーティションが
オブジェクト形式マクロとして定義された識別子を含めることができた
禁止された
English 日本語 中文(简体) 中文(繁體)