C++20模块用export module声明接口单元、import替代#include,实现编译模型重构;接口单元导出内容供外部使用,未export的默认私有,import必须位于翻译单元最前面。

用 C++20 模块(Modules)替代头文件,核心就两点:用 module 声明接口单元,用 import 替代 #include。它不是语法糖,而是编译模型的重构——模块被单独编译成二进制接口文件(如 .pcm / .ifc),不再重复解析、宏污染或依赖顺序问题。
怎么写一个基础模块?
模块分接口单元(interface unit)和实现单元(implementation unit)。接口单元导出供别人用的内容,必须以 export module 开头:
export module math_utils;
export int add(int a, int b) { return a + b; }
export namespace math {
constexpr double PI = 3.14159;
}
注意:
• export 只修饰你想对外暴露的声明或定义(函数、变量、类、命名空间等)
• 不加 export 的内容默认私有,仅在本模块内可用
• 模块名(math_utils)是逻辑标识,不要求与文件名一致,但建议保持一致便于维护
立即学习“C++免费学习笔记(深入)”;
怎么在其他文件里使用模块?
用 import,不是 #include,且必须出现在翻译单元最前面(不能在头文件里、不能在 #include 后面):
import math_utils;
int main() {
return add(2, 3); // OK
// std::cout << math::PI; // 需要额外 import(见下条)
}
常见组合:
• import std; —— 导入标准库统一模块(C++23 起更成熟,C++20 多数编译器仍实验性支持)
• import —— 导入传统头文件的“头文件单元”(需编译器开启对应选项,如 Clang 的 -fmodules-ts 或 MSVC 的 /experimental:module)
• import "my_header.h"; —— 导入自定义头文件作为模块(需先将其显式构建为头文件单元)
实际编译时要注意什么?
模块不是“写了就能跑”,编译流程变了:
- 先编译接口单元生成模块接口文件(.ifc / .pcm):
clang++ -std=c++20 -x c++-system-header -fmodules -fimplicit-modules -fimplicit-module-maps -c math_utils.cppm -o math_utils.ifc - 再编译主程序,告诉编译器去哪里找 .ifc:
clang++ -std=c++20 -fmodules -fmodule-file=math_utils.ifc main.cpp -o main - MSVC 更简单些:直接
cl /std:c++20 /experimental:module /EHsc main.cpp math_utils.ixx,它自动处理依赖
关键提醒:
• 所有使用模块的源文件必须用相同标准(如都用 -std=c++20)
• 模块接口文件路径需被正确识别(Clang 常需 -fmodule-map-file 或 -fprebuilt-module-path)
• 不同编译器对模块的支持程度不同:MSVC 最早落地,GCC 11+ 支持较稳,Clang 需 13+ 且配置较细
模块能完全取代头文件吗?现阶段怎么过渡?
不能立刻全切。建议分三步走:
- 新项目直接用模块写核心逻辑(工具类、算法模块等)
- 旧项目保留头文件,但把稳定、无宏、无模板特化的部分逐步封装成模块(例如把
utils.h改成utils.cppm) - 第三方库暂不强求——除非它已提供模块化版本(如 Boost 1.82+ 开始实验性支持模块)
特别注意:模板定义必须在接口单元中可见(不能像头文件那样只声明),所以模块里写模板函数/类时,定义通常得和声明一起 export 出来。










