c++20模块是独立编译单元,需用module/import声明和导入,禁用#include混用;接口文件以export module开头,实现文件用module name;;各编译器模块二进制不兼容且参数差异大;export仅控可见性,odr问题仍需inline等配合;构建系统依赖管理是关键难点。

模块声明和导入必须用 module 和 import,不能混用头文件包含
模块不是头文件的替代语法糖,而是完全独立的编译单元机制。一旦用了 module 声明,就不能再用 #include 导入该模块内容;反过来,传统头文件也不能直接 import(除非显式导出为模块接口)。
- 模块接口文件(如
math.mod.cpp)必须以export module math;开头,且只能有一条module声明 - 模块实现文件(如
math.impl.cpp)用module math;(不带export),且不能有#include同名模块的头文件 - 主程序中写
import math;,不是import "math.mod.cpp";或#include "math.h" - Clang/GCC/MSVC 对模块文件后缀无强制要求,但推荐用
.cppm或.ixx区分,避免被旧构建系统误当普通源码编译
MSVC 的 /interface 和 Clang 的 -x c++-system-header 参数差异很大
不同编译器生成模块二进制(.ifc / .pcm)的方式不兼容,且命令行参数含义容易混淆。
- MSVC 必须用
/interface编译模块接口文件,生成.ifc;用/headerUnit处理传统头文件模块化(非标准,慎用) - Clang 需先用
-x c++-module -Xclang -emit-module-interface生成.pcm,再在主编译中加-fprebuilt-module-path=. - GCC 13+ 支持但默认不启用模块,需加
-fmodules-ts,且目前不支持模块分区(module :private) - 所有编译器都要求模块接口文件必须先于依赖它的源文件编译完成,CMake 中要用
target_compile_features(... PRIVATE cxx_modules)并显式设置依赖顺序
export 关键字只控制符号可见性,不解决 ODR 违规
模块里 export 的函数/类,只是让导入者能看见;但如果多个模块导出了同名全局变量或 inline 函数,仍可能触发 ODR(One Definition Rule)错误。
- 全局变量必须加
inline才能在多处export而不冲突,比如:export inline int counter = 0; - 模板定义天然 inline,可放心
export template<typename t> T add(T a, T b) { return a + b; }</typename> - 不要在模块接口里
exportstatic变量或未命名命名空间内容——它们无法被外部访问,且可能引发链接失败 - 如果模块 A 导入了模块 B,又想把 B 的一部分接口透传给使用者,得用
export import B;,而不是重复exportB 里的东西
构建系统没对齐时,import 会静默失败或报 module file not found
这不是代码写错了,而是构建流程没把模块编译产物路径正确暴露给后续编译步骤。
立即学习“C++免费学习笔记(深入)”;
- CMake 3.27+ 提供
add_module(),但多数项目还在用手工add_library()+ 自定义命令,容易漏掉set_property(GLOBAL PROPERTY USE_FOLDERS ON)类的元信息同步 - Ninja 生成器对模块依赖感知弱,有时会跳过模块接口编译,建议搭配
cmake --build . --verbose确认是否执行了.ifc生成命令 - VS2022 在解决方案配置里要手动勾选“启用 C++20 模块支持”,否则即使代码合法,也会当成普通源码处理
- 模块文件路径区分大小写,Linux/macOS 下
import Math;和import math;是两个模块,而 Windows 可能不报错但行为不一致
模块真正的复杂点不在语法,而在整个工具链对“编译单元边界”的重新定义。一个 import 失败,八成不是你写错了,是某个中间产物没落进正确的搜索路径,或者编译器版本之间悄悄改了二进制格式。











