在 C++20 之后,模块(module)成为了官方的语言特性,旨在解决传统头文件导致的编译依赖、重复编译以及编译速度慢等问题。本文将带你完整实现一个小型模块化项目,演示如何编写、编译以及使用模块。
1. 先决条件
- 支持 C++20 的编译器(如 GCC 10+、Clang 11+、MSVC 16.10+)
clang或gcc的命令行工具- 基本的 C++ 编译经验
如果你使用的是 VS Code,安装 C++ 插件即可;如果是 CLion 或其他 IDE,只需在 CMakeLists.txt 或项目设置中启用 C++20 并开启模块支持。
2. 项目结构
mod_example/
├─ src/
│ ├─ math/
│ │ ├─ adder.cpp
│ │ └─ adder.hpp
│ └─ main.cpp
├─ include/
│ └─ math/
│ └─ adder.hpp
└─ CMakeLists.txt
math/adder.hpp定义了模块接口math/adder.cpp是模块实现main.cpp直接使用模块CMakeLists.txt负责编译配置
3. 编写模块接口(adder.hpp)
// include/math/adder.hpp
export module math.adder; // 公开模块名
export struct Adder {
int a;
int b;
// 默认构造函数
export constexpr Adder(int a_, int b_) : a(a_), b(b_) {}
// 成员函数:返回两数之和
export constexpr int sum() const noexcept { return a + b; }
};
说明
export module math.adder;:声明并公开模块math.adder。任何使用该模块的翻译单元都可以看到后面export的内容。export关键字位于结构体及其成员前,表示这些成员对外可见。
4. 编写模块实现(adder.cpp)
// src/math/adder.cpp
module math.adder; // 只需导入模块内部定义
// 这里可以添加更复杂的实现、内部函数等
对于纯粹的头文件实现,
adder.cpp可以为空。若有需要,仍可以在此文件中实现非导出成员或内部细节。
5. 主程序(main.cpp)
// src/main.cpp
import math.adder; // 直接导入模块,类似 `#include <math/adder.hpp>`
#include <iostream>
int main() {
Adder add{10, 32};
std::cout << "10 + 32 = " << add.sum() << std::endl;
return 0;
}
6. CMake 配置(CMakeLists.txt)
cmake_minimum_required(VERSION 3.20)
project(ModularExample LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 启用模块编译(对 MSVC 可选)
set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$<CONFIG:Debug>:Debug>)
add_library(math_adder SHARED src/math/adder.cpp)
target_include_directories(math_adder PUBLIC include) # 提供头文件路径
target_compile_options(math_adder PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/bigobj>
$<$<CXX_COMPILER_ID:Clang>:-fmodules-ts>
$<$<CXX_COMPILER_ID:GCC>:-fmodules-ts>
)
add_executable(modular_main src/main.cpp)
target_link_libraries(modular_main PRIVATE math_adder)
要点
-fmodules-ts是 Clang/GCC 的实验性模块支持编译标志;MSVC 已经默认支持。- 使用
target_include_directories将include目录加入编译器搜索路径。 - 目标
math_adder是共享库,也可以改为STATIC或OBJECT。
7. 编译与运行
$ mkdir build && cd build
$ cmake .. -DCMAKE_BUILD_TYPE=Release
$ cmake --build .
$ ./modular_main
10 + 32 = 42
若一切顺利,你会看到 10 + 32 = 42 的输出。模块化编译后,如果你仅仅更改 adder.hpp,只会触发对应模块的重新编译,其他文件保持不变,从而提升整体编译速度。
8. 进阶:多模块协作
假设你需要一个 math.subtract 模块:
- subtract.hpp 与 subtract.cpp 同样结构
- 在 main.cpp 使用
import math.subtract; - 在
CMakeLists.txt中添加新的库math_subtract并链接至主程序
这体现了模块化项目的可组合性:各模块相互独立,彼此间只通过导入(import)而不是头文件包含。
9. 结语
C++20 的模块系统极大简化了大型项目的构建流程,减少了头文件带来的冗余。通过上述示例,你已掌握:
- 如何声明、实现与使用模块
- 在 CMake 中配置模块编译
- 通过模块实现编译加速
在实际项目中,你可以进一步探讨:
- 模块缓存(module interface unit)如何让编译更快
- 与第三方库的模块化包装
- 模块与
precompiled headers (PCH)的关系
欢迎尝试将更多业务逻辑拆分为独立模块,体验 C++20 时代的编译效率革命。