C++ 专家(13):C++20 模块深入——模块系统、ABI 影响、编译性能
更新时间:2026-09-01。本文是
languages/cpp/expert/专家层第 13 篇,接 CRTP。C++20 模块是替代头文件的新方案,消除了宏污染和编译性能问题。本文深入模块的 ABI 影响、全局模块片段、私有模块分区、以及模块下的编译性能。
本文要回答的问题
- 模块接口单元和模块实现单元怎么分开?
- 模块的 ABI 影响是什么?模块导出类型和传统头文件导出类型能混用吗?
- 全局模块片段(global module fragment)是什么?
- 私有模块分区(private module partition)怎么用?
一、模块接口单元 vs 模块实现单元
cpp
// math.cpp(模块接口单元)
export module math;
// 导出接口
export int add(int a, int b);
export class Calculator {
public:
int multiply(int a, int b);
int divide(int a, int b);
private:
int internal_helper(int a);
};cpp
// math_impl.cpp(模块实现单元)
module math; // 属于 math 模块,但不需要 export
int add(int a, int b) {
return a + b;
}
int Calculator::multiply(int a, int b) {
return a * b;
}
int Calculator::divide(int a, int b) {
return internal_helper(a) / b;
}
int Calculator::internal_helper(int a) {
return a * 2;
}好处: 接口文件和实现文件分离,但接口文件不需要 #include 保护,也不需要前向声明。
二、全局模块片段
cpp
// 在模块文件开头,用 module; 声明
module; // 全局模块片段开始
#include <vector> // 传统头文件只能在这里 include
#include <string>
export module my_module; // 模块声明开始
// 模块导出
export std::vector<int> process(const std::string& s);规则:
- 全局模块片段里的
#include不会污染模块的导出接口 - 导入方看不到这些 include 的内容
- 只能用于包含 C 标准库头文件或传统 C++ 头文件
三、私有模块分区
cpp
// 私有模块分区:对外不可见,模块内部使用
// 主模块:math.cpp
export module math;
export import :internal; // 导入私有分区
export int compute(int x);cpp
// 私有分区:math-internal.cpp
module math:internal; // 私有模块分区
export int helper(int x) {
return x * 2;
}四、ABI 影响
cpp
// 模块导出类型和传统类型在 ABI 上兼容吗?
// 答案:同一编译器,兼容。不同编译器,不兼容。
// module_a.cpp
export module module_a;
export struct Point {
int x, y;
};
// module_b.cpp
import module_a;
Point p{1, 2}; // 同一编译器,没事ABI 限制:
- 同一编译器:模块导出类型和传统类型 ABI 兼容
- 不同编译器:模块的二进制格式不兼容
- 动态库中导出模块类型:需要同一编译器
五、编译性能
传统头文件:
a.h → b.h → c.h (每个文件都要解析头文件,重复解析)
main.cpp 包含 a.h b.h c.h → 解析三次头文件
模块:
a.cppm → 编译一次,生成 BMI(Binary Module Interface)
b.cppm → 编译一次,生成 BMI
c.cppm → 编译一次,生成 BMI
main.cpp → 导入三个 BMI,直接读取,不解析编译速度提升:
- 头文件被 N 个源文件包含 → 解析 N 次
- 模块被 N 个源文件导入 → 编译一次,导入 N 次(直接读取 BMI)
- 大型项目编译速度提升 2-5x
六、模块和传统代码共存
cpp
// 模块可以导入传统头文件
module;
#include <vector>
export module my_module;
// 传统头文件也可以导入模块(编译器支持)
// header.h
import my_module; // 某些编译器支持七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 不同编译器模块 BMI 不兼容 | 链接错误 | 同一编译器编译所有模块 |
| 模块里不能有宏 | 模块不导出宏 | 用 constexpr 或内联函数替代 |
| 全局模块片段里才能 include | 在模块声明后 include 错 | 所有 include 放在全局模块片段 |
| 模块的编译顺序 | 导入模块必须之前编译好 | 确保编译顺序正确 |
相关与延伸
下一篇:Ranges 深入——视图、适配器、lazy evaluation、ownership;C++20 模块入门,见 模块系统。
一句话总结
C++20 模块深入:模块接口单元(export module)和模块实现单元(module)分开,接口文件更干净;全局模块片段(module;)放传统 include,不污染导出接口;私有模块分区(module:internal)对外不可见,模块内部复用;同一编译器模块 ABI 兼容,不同编译器不兼容;模块编译一次后直接读取 BMI,大型项目编译速度提升 2-5x。