模块 (自 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 module-name module-partition (可选) attr (可选) ; |
(1) | ||||||||
export declaration |
(2) | ||||||||
export { declaration-seq (可选) } |
(3) | ||||||||
export (可选) import module-name attr (可选) ; |
(4) | ||||||||
export (可选) import module-partition attr (可选) ; |
(5) | ||||||||
export (可选) import header-name attr (可选) ; |
(6) | ||||||||
module;
|
(7) | ||||||||
module : private;
|
(8) | ||||||||
[编辑] 模块声明
一个翻译单元可能有一个模块声明,在这种情况下,它被视为一个模块单元。如果提供,模块声明必须是翻译单元的第一个声明(全局模块片段除外,稍后会介绍)。每个模块单元都与一个模块名称(以及可选的分区)相关联,这些名称在模块声明中提供。
export (可选) module module-name module-partition (可选) attr (可选) ; |
|||||||||
模块名称由一个或多个用点分隔的标识符组成(例如:mymodule
、mymodule.mysubmodule
、mymodule2
...)。点本身没有内在含义,但它们被非正式地用来表示层次结构。
如果模块名称或模块分区中的任何标识符被定义为一个类似对象的宏,则程序格式错误。
一个命名模块是具有相同模块名称的模块单元的集合。
其声明具有 export 关键字的模块单元被称为模块接口单元;所有其他模块单元被称为模块实现单元。
对于每个命名的模块,必须恰好有一个模块接口单元,它不指定任何模块分区;这个模块单元被称为主模块接口单元。导入相应的命名模块时,其导出的内容将可用。
// (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 declaration |
|||||||||
export { declaration-seq (可选) } |
|||||||||
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 module-name attr (可选) ; |
|||||||||
在给定命名模块的模块接口单元中导出的所有声明和定义都将在使用导入声明的翻译单元中可用。
导入声明可以在模块接口单元中导出。也就是说,如果模块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 header-name attr (可选) ; |
|||||||||
头文件单元是从头文件合成的独立翻译单元。导入头文件单元将使所有其定义和声明都可访问。预处理宏也是可访问的(因为导入声明被预处理器识别)。
然而,与 #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'.
模块分区表示恰好一个模块单元(两个模块单元不能指定相同的模块分区)。它们只在命名模块内部可见(命名模块外部的翻译单元不能直接导入模块分区)。
相同命名模块的模块单元可以导入模块分区。
export (可选) import module-partition attr (可选) ; |
|||||||||
/////// 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 module-partition attr (可选) ; |
|||||||||
/////// 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
如果 实体的两个声明 附加到不同的模块,程序将格式错误;如果两者都不能从对方访问,则不需要任何诊断。
/////// 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).
[编辑] 注释
特性测试 宏 | 值 | Std | 特性 |
---|---|---|---|
__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 | 模块名称和模块分区可以 包含定义为类似对象的宏的标识符 |
禁止 |