文章目录
- [第一章 C++20核心语法特性](#第一章 C++20核心语法特性)
-
- [1.4 模块 (Modules)](#1.4 模块 (Modules))
-
- [1.4.1 隔离原理](#1.4.1 隔离原理)
- [1.4.2 示例](#1.4.2 示例)
- [1.4.3 模块与头文件比较](#1.4.3 模块与头文件比较)
本文记录C++20新特性之模块 (Modules)。
第一章 C++20核心语法特性
1.4 模块 (Modules)
在C++20之前,C++和C包含头文件时,都使用#include包含,这是一种文本替换机制,主要可以概括三大痛点。
1 编译速度极慢:#include 仅仅是简单的文本复制。如果一个头文件被 100 个源文件包含,编译器就必须解析这个头文件 100 次。对于庞大的标准库(如 或 ),这带来了巨大的重复工作量。
2 宏隔离性差:头文件中定义的宏(Macros)会"泄漏"到包含它的源文件中,甚至影响后续包含的其他头文件,导致难以排查的命名冲突和 Bug。
3 依赖地狱:头文件的包含顺序可能会影响代码行为,且很难清晰地看到一个文件到底依赖了哪些具体符号.
C++20引入了模就是为了提供一种语义化、编译一次、隔离性强的代码共享机制,这是C++诞生40年来首次对代码头文件组织方式的一次变革。
1.4.1 隔离原理
模块引入了两个关键字 export 和 import
独立编译:模块单元(Module Unit)被编译成一种二进制格式(BMI, Binary Module Interface)。当其他文件 import 这个模块时,编译器直接读取这个预编译好的二进制接口,而不是重新解析源代码。这使得编译速度呈数量级提升。
真正的隔离:模块内部定义的宏、未导出的函数和类型,对外部是完全不可见的。import 一个模块不会像 #include 那样把一堆垃圾符号倒进你的全局命名空间。
初始化顺序:模块提供了更明确的初始化顺序保证,减少了"静态初始化顺序大灾难"发生的概率。
1.4.2 示例
下面示例中,定义一个模块math_utils.ixx,并在主模块中完成调用。
cpp
export module math; // 声明 这是一个 math 模块
// 2 导出 export 需要给外部使用的部分
export int add(int a, int b)
{
return a + b;
}
// 没有 export 的函数,外部无法访问
int helper(int a)
{
return a * 2;
}
// 导出使用了非导出函数的函数
export int double_add(int a, int b)
{
return helper(add(a, b));
}
在主模块中导入 自定义模块:
cpp
import math; // 导入自定义的模块
void test()
{
std::cout << "add() " << add(1, 2) << std::endl;
// 3
std::cout << "double_add() " << double_add(5, 3) << std::endl;
// 16
}
示例2:模块分区
对于大型项目,一个模块可能非常大,我们可以将模块分为多个分区,在一个主模块中汇总分模块。下面将math模块分为 sub和add分模块,然后在math主模块导出分模块内容。
math_add.ixx (分区文件)
cpp
export module math:add; // 声明属于 math 模块的 add 分区
export int add(int a, int b) { return a + b; }
math_sub.ixx (分区文件)
cpp
export module math:sub; // 声明属于 math 模块的 sub 分区
export int sub(int a, int b) { return a - b; }
math.ixx (主模块接口)
cpp
export module math; // 定义主模块
export import :add; // 导出并重新发布 add 分区
export import :sub; // 导出并重新发布 sub 分区
1.4.3 模块与头文件比较
下面是模块和头文件的比较。
