模块接口单元(.ixx)必须以export module开头且仅有一个模块声明,用于导出契约;实现需分离至模块实现单元,导入须显式声明依赖,否则链接失败。

模块接口单元文件(.ixx)是C++20模块的入口声明点
它不是头文件的替代品,也不是单纯“把 #include 换成 export module”就能跑通的东西。真正的模块接口单元必须以 export module 开头,且只能有一个模块声明;编译器靠这个识别“这是模块定义的起点”。常见错误是把它写成普通源文件——比如漏掉 export,或混用 import 和 export 在同一行,结果链接时报 undefined reference to 'xxx',因为符号根本没导出。
实操建议:
- 文件扩展名必须是
.ixx(MSVC 强制要求;Clang/GCC 虽可配-x c++-system-header,但不推荐绕过约定) -
export module mylib;必须是第一行非注释、非空行代码 - 只在接口单元里写
export声明,比如export void foo();或export namespace mylib { ... } - 不要在
.ixx里写函数实现体(除非是inline或模板),否则会破坏模块的二进制接口稳定性
如何让老代码(头文件+源文件)迁移到模块接口单元
不能直接把 mylib.h 和 mylib.cpp 合并成一个 .ixx 文件。模块要求“声明与实现分离”更严格:接口单元只负责导出契约,实现得放进单独的模块实现单元(.cpp 或 .cxx),且要用 module mylib;(无 export)开头。
常见踩坑点:
立即学习“C++免费学习笔记(深入)”;
- 把
#include <vector></vector>直接抄进.ixx—— 错。应改用import std;(C++23)或import std.core;(C++20 实验性,GCC/Clang 支持有限);更稳妥的是用export import std;显式转发 - 在接口单元里
import自己的实现模块 —— 不合法。模块导入关系必须是单向的:接口单元可以import其他模块(如std),但不能import同名模块的实现部分 - 忘记在实现单元里写
module mylib;—— 导致该文件被当成普通翻译单元,函数不会绑定到模块,链接失败
模块名冲突和跨平台兼容性问题很现实
模块名不是路径,也不是命名空间。写 export module utils::string; 看似清晰,但 MSVC 会把它当做一个叫 utils::string 的完整模块名(含冒号),而 Clang 可能报错不支持嵌套模块语法。实际项目中,建议用扁平命名: export module mylib_string;。
另外,模块二进制接口(BMI)目前没有跨编译器标准:MSVC 生成的 .ifc,Clang 读不了;Clang 的 .pcm,GCC 也不认。这意味着你没法把模块编译产物当通用库分发。
所以真实使用场景里:
- 团队统一用 MSVC?可以放心上
.ixx+/interface编译开关 - 要支持 GCC/Clang?优先用模块接口单元做内部解耦,但对外仍提供传统头文件封装(用
export module mylib:compat;+export #include "mylib.h") - CI 流水线里必须显式指定模块缓存路径(如
/module:cache_dir ./modules),否则重编译时 BMI 找不到,反复全量构建
为什么 import 之后仍然找不到函数?
最常忽略的一点:模块导入不等于符号可见。即使写了 import mylib;,如果接口单元里没对那个函数加 export,它就只是模块内部可用,不会暴露给导入方。错误现象是编译通过、链接失败,报 undefined reference to 'mylib::do_work()'。
另一个隐蔽原因:模块依赖未显式声明。比如 mylib_string.ixx 用了 std::string_view,但没写 import std;,那么即使调用方 import mylib_string;,也无法解析 string_view 类型——模块的依赖必须在接口单元里“自包含”声明。
检查步骤:
- 确认目标函数/类前有
export关键字(或包在export namespace内) - 确认接口单元里所有用到的标准库类型,都对应
import std;或具体子模块 - 用
cl /showIncludes /module:reference mylib.ixx(MSVC)或clang++ -fmodules -x c++-system-header -E -查看模块依赖图










