命名空间
变体
操作

模块 (C++20 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (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)
1) 模块声明。声明当前翻译单元是模块单元
2,3) 导出声明。导出declarationdeclaration-seq中的所有命名空间范围声明。
4,5,6) 导入声明。导入模块单元/模块分区/头单元。
7) 启动全局模块片段
8) 启动私有模块片段

[编辑] 模块声明

一个翻译单元可以有一个模块声明,在这种情况下它被认为是模块单元。如果提供模块声明,它必须是翻译单元的第一个声明(除了稍后介绍的全局模块片段)。每个模块单元都与模块声明中提供的模块名(以及可选的模块分区)相关联。

export(可选) module module-name module-partition (可选) attr (可选) ;

模块名由一个或多个用点分隔的标识符组成(例如:mymodulemymodule.mysubmodulemymodule2...)。点没有内在含义,但它们非正式地用于表示层次结构。

如果模块名或模块分区中的任何标识符被定义为类对象宏,则程序是格式错误的。

命名模块是具有相同模块名的模块单元的集合。

声明带有关键字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;

preprocessing-directives (可选)

module-declaration

如果模块单元有全局模块片段,则其第一个声明必须是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;

declaration-seq (可选)

私有模块片段结束了模块接口单元中可能影响其他翻译单元行为的部分。如果模块单元包含私有模块片段,它将是其模块的唯一模块单元。

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).

[编辑] 注意

特性测试 标准 特性
__cpp_modules 201907L (C++20) 模块 — 核心语言支持
__cpp_lib_modules 202207L (C++23) 标准库模块 stdstd.compat

[编辑] 关键词

private, module, import, export

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
CWG 2732 C++20 关于可导入头文件是否可以
对导入点的预处理器状态做出反应,不明确
不作反应
P3034R1 C++20 模块名和模块分区可以
包含定义为类对象宏的标识符
已禁止