**C++20 模块化编程:从头到尾实现一个模块化项目**

在 C++20 之后,模块(module)成为了官方的语言特性,旨在解决传统头文件导致的编译依赖、重复编译以及编译速度慢等问题。本文将带你完整实现一个小型模块化项目,演示如何编写、编译以及使用模块。

1. 先决条件

  • 支持 C++20 的编译器(如 GCC 10+、Clang 11+、MSVC 16.10+)
  • clanggcc 的命令行工具
  • 基本的 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_directoriesinclude 目录加入编译器搜索路径。
  • 目标 math_adder 是共享库,也可以改为 STATICOBJECT

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.hppsubtract.cpp 同样结构
  • main.cpp 使用 import math.subtract;
  • CMakeLists.txt 中添加新的库 math_subtract 并链接至主程序

这体现了模块化项目的可组合性:各模块相互独立,彼此间只通过导入(import)而不是头文件包含。

9. 结语

C++20 的模块系统极大简化了大型项目的构建流程,减少了头文件带来的冗余。通过上述示例,你已掌握:

  • 如何声明、实现与使用模块
  • 在 CMake 中配置模块编译
  • 通过模块实现编译加速

在实际项目中,你可以进一步探讨:

  • 模块缓存(module interface unit)如何让编译更快
  • 与第三方库的模块化包装
  • 模块与 precompiled headers (PCH) 的关系

欢迎尝试将更多业务逻辑拆分为独立模块,体验 C++20 时代的编译效率革命。

发表评论