模块接口单元必须以 export module x; 开头,显式导出符号;实现单元用 module x; 关联同名模块,不可 export;未导出内容不污染命名空间,宏不跨模块传播;构建系统需区分编译方式。

模块接口单元必须用 export module 声明
接口不是靠文件名或注释约定的,而是由语法强制:只有以 export module X; 开头的文件(如 math.ixx)才被视为模块接口单元。它负责声明哪些东西能被外部看到。不加 export 的函数、类、变量,哪怕写在接口文件里,也仅限模块内部使用。
常见错误是把整个接口文件写成普通头文件风格,漏掉 export module,或者写成 module math;(没 export)——这样编译器不会生成模块接口,import math; 会直接报错 module not found。
-
export module Math;是必需的第一行,不能有前置代码(除非是全局模块片段) - 接口文件中所有对外公开的符号,必须显式用
export修饰,包括函数定义、类声明、变量声明 - 支持导出内联函数和模板,无需额外
inline或extern template声明
实现单元用 module X; 关联同一模块名
实现文件(如 math_impl.cpp)不导出任何东西,只提供接口中声明的函数体或类成员实现。它通过 module Math;(与接口同名,但无 export)声明自己属于该模块,从而让编译器把二者合并为一个逻辑模块。
容易踩的坑是实现文件用了错误的模块名,比如写成 module Utils;,导致链接时报 undefined reference to 'add' ——因为编译器认为这是另一个模块,不会把实现和接口关联起来。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 实现单元中不能出现
export,否则编译失败 - 可以有多个实现文件,只要都声明
module Math;,它们就共同构成Math模块的实现部分 - 实现文件里可以包含传统头文件(如
#include <vector></vector>),但不能import自己所属的模块(循环依赖)
导出控制粒度比头文件精细得多
头文件里“暴露即可见”,而模块允许你在接口文件里混写私有辅助函数、静态变量、未导出类——只要不加 export,它们就不会污染导入者的命名空间,也不会出现在模块接口二进制中。
比如你写了一个内部工具函数 detail::clamp 供 export int safe_div(int, int) 使用,它完全不会被 import Math; 的用户感知到,也不参与 ODR 检查。这在头文件时代只能靠命名约定(如加 detail_ 前缀)或匿名命名空间,效果弱且不可靠。
- 未导出的模板实例化不会触发隐式实例化,减少编译膨胀
- 宏定义默认不跨模块传播,避免传统头文件的宏污染问题
- 若需导出宏,必须封装进
export inline constexpr变量或export consteval函数
构建系统必须区分接口与实现单元的编译方式
Clang 和 MSVC 对模块接口单元(.ixx 或 .cppm)要求先单独编译为模块接口文件(.pcm 或 .ifc),再让其他文件导入;而实现单元按普通翻译单元处理。CMake 需显式设置 set_source_files_properties(... PROPERTIES CXX_MODULE_INTERFACE ON),否则会当成普通源码编译失败。
最常被忽略的一点:模块接口单元不能被链接进最终可执行文件——它只生成接口描述,真正代码都在实现单元里。如果误把 math.ixx 加进 target_sources 而没设属性,链接时会缺符号;如果忘了在实现单元里定义接口中声明的函数,编译能过,链接直接失败。
- MSVC 默认用
.ixx后缀识别接口单元;Clang 推荐.cppm,但需加-x c++-system-header参数 - 接口单元不能有
main()或全局副作用代码,否则模块导入行为不可预测 - 调试时,断点打在接口文件里的
export函数上可能无法命中——实际执行的是实现单元中的定义
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










