如何创建无表头库?C++20模块能否彻底移除代码中的#include?
能否用C++20模块彻底替代#include?
这是个戳中C++痛点的好问题——毕竟模块特性的核心目标之一就是解决头文件机制带来的编译慢、重复定义、宏污染等问题,但真要彻底抛弃#include,尤其是用import boost.asio;替代#include <boost/asio.hpp>这类第三方库引用,现实中还有不少绕不开的障碍。
模块时代的库安装逻辑变化
先对比下传统和模块化的库安装流程:
传统流程
- 将所有源文件(
.cpp)编译为目标文件(.o) - 把目标文件打包为静态/动态库(
.a或.so) - 头文件安装到
/usr/include目录 - 库文件安装到
/usr/lib目录
C++20模块的理想流程
- 编译源文件时,每个文件会生成目标文件(
.o)和编译模块接口文件(CMI)(比如GCC用.gcm作为后缀) - 打包目标文件为库文件
- 库文件安装到
/usr/lib目录 - CMI文件安装到指定的模块路径
直接用CMI替代头文件的现实问题
看起来流程很顺畅,但实际发布带CMI的库,会遇到一堆棘手的问题:
- 缺少标准的CMI存放目录:目前业界还没有统一的标准路径来存放CMI文件(虽然有推测可能会是
/usr/include/c++-modules),不同编译器、发行版可能各自为政,导致部署混乱。 - 编译器间的CMI不兼容:不同编译器生成的CMI格式完全不通用——GCC的
.gcm和Clang的CMI文件无法互相识别,库开发者需要为每个主流编译器维护一套对应的CMI,维护成本大幅上升。 - 编译选项敏感引发隐性Bug:如果编译依赖模块B的模块A时,使用了和编译模块B不同的编译选项(比如宏定义、优化等级、C++标准版本),这种差异可能不会立刻触发编译错误,但会埋下难以排查的隐性Bug。
显然,用预先生成的CMI直接替代头文件,目前并不是一个理想的方案。
那如何才能彻底移除头文件?
先明确:本次讨论不涉及C标准库,因为**模块化的C标准库是在C++23中才正式引入的**。
要彻底抛弃#include,需要整个生态的协同推进:
- 库开发者全面拥抱模块语法:库本身需要提供完整的模块接口定义(即用
export module编写的模块源文件),而不是依赖“头文件转模块”的过渡方案。 - 统一生态标准:需要业界统一CMI的存放路径、跨编译器的兼容方案;或者更理想的是,编译器支持直接从模块接口源文件(比如
.cppm)按需编译,而非依赖预先生成的CMI。 - 工具链适配模块流程:构建系统(CMake、Meson等)、包管理器(Conan、vcpkg等)需要完全适配模块的依赖管理、编译分发逻辑,能自动处理模块接口文件或兼容的CMI。
但目前来看,大部分主流第三方库(包括Boost的诸多组件)还在逐步适配模块特性,离完全抛弃头文件还有相当长的路要走。现阶段更多是混合模式:部分新代码用模块,依赖老库的部分还是得保留#include。
内容的提问来源于stack exchange,提问作者aleck099




