在 C++20 中,模块(Modules)提供了一种更高效、更安全的方式来组织和编译大型代码库。相比传统的头文件系统,模块消除了预处理器宏、重复编译以及大量的编译时间开销。本文将介绍模块的基本概念、如何定义一个模块、如何导入模块以及如何管理模块依赖,以帮助你在实际项目中快速上手。
1. 模块的基本概念
- 模块单元(Module Unit):模块的基本单元,通常对应一个
.cpp或.ixx文件。 - 导出(Export):通过
export关键字将模块内部的符号暴露给其他模块或编译单元。 - 模块接口(Module Interface):定义了模块对外暴露的 API。接口文件以 `export module ;` 开头。
- 模块实现(Module Implementation):实现了模块内部逻辑,但不对外暴露的部分。实现文件以 `module ;` 开头。
2. 定义一个模块
假设我们要创建一个名为 math_utils 的模块,提供一些数学工具函数。
2.1 模块接口文件 math_utils.ixx
export module math_utils;
export int add(int a, int b) {
return a + b;
}
export int subtract(int a, int b) {
return a - b;
}
2.2 模块实现文件 math_utils_impl.cpp
module math_utils;
#include <iostream>
int multiply(int a, int b) {
return a * b;
}
int divide(int a, int b) {
if (b == 0) throw std::runtime_error("division by zero");
return a / b;
}
注意:实现文件不需要使用
export,因为它们不对外暴露。
3. 导入并使用模块
在主程序中,我们只需要导入模块接口文件即可使用其中导出的符号。
import math_utils;
import <iostream>;
int main() {
std::cout << "add: " << add(3, 4) << '\n';
std::cout << "subtract: " << subtract(10, 6) << '\n';
return 0;
}
3.1 编译指令
不同编译器的编译命令略有差异,以下以 clang++ 为例:
# 编译模块接口
clang++ -std=c++20 -fmodules-ts -c math_utils.ixx -o math_utils.o
# 编译模块实现
clang++ -std=c++20 -fmodules-ts -c math_utils_impl.cpp -o math_utils_impl.o
# 生成模块缓存
clang++ -std=c++20 -fmodules-ts -fmodule-map-file=modules.map -c main.cpp
# 链接
clang++ -std=c++20 -fmodules-ts -fmodule-map-file=modules.map math_utils.o math_utils_impl.o main.o -o app
modules.map是一个可选文件,用于定义模块名称与文件路径的映射,帮助编译器定位模块实现。
4. 管理模块依赖
4.1 依赖链
如果 math_utils 需要使用标准库中的 `