首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

CMake构建C++20 Module实例(使用MSVC)

  • 25-03-07 15:01
  • 3708
  • 7486
blog.csdn.net

提醒: 本文中的例子是在 MSVC(Microsoft Visual Studio 2022 Preview)编译环境上面测试通过的, 截止文章更新时, 没有在 Clang/GCC 上面验证通过.
本文所有的源代码以及工程配置均可下载.

背景

在传统的 C++ 编译过程中, 代码的构建通常分为三个主要步骤:

  • 预处理: 处理 #include 等指令, 将头文件内容展开并插入到代码中.
  • 编译: 将预处理后的代码转换为机器可以理解的目标文件.
  • 链接: 将目标文件链接成完整的可执行程序.

这种流程对于小型项目非常高效, 但随着项目规模的扩大, 其局限性逐渐显现. 比如:

  • 冗长的预处理: 头文件中的嵌套依赖会导致编译器在每次编译时都需要重复展开大量文件, 极大地浪费了时间和资源.
  • 容易发生宏冲突: 宏仅仅是文本替换, 缺乏 C++ 的语义支持. 当不同的头文件中存在相同的宏定义时, 极易导致难以调试的问题.
  • 头文件管理复杂: 开发者不得不通过 #pragma once 或头文件保护机制来避免重复定义问题, 这无形中增加了代码维护的复杂性.

为了解决这些问题, C++20 引入了**模块(Module)**特性, 这是一种替代传统头文件的新方案. 模块具备以下显著优势:

  • 高效编译: 模块只会被编译一次, 避免了头文件的重复展开.
  • 防止冲突: 模块内容被隔离, 不同模块间的名称不会互相干扰.
  • 清晰组织: 模块明确区分哪些内容是导出的, 哪些仅在模块内部使用, 从而更好地表达代码逻辑.

通过模块, C++ 不仅提升了编译效率, 还为大型项目提供了更现代化的代码组织方式. 在本文中, 我们将通过多个实例, 带你逐步掌握 C++20 模块的使用方法, 帮助你从传统的头文件世界迈入模块化的新时代.

编译器对模块的支持

工具最低版本要求
CMake3.28
GCC14.0
Clang/LLVM16.0
MSVC2022 (cl17.4)

目前 MSVC 对 C++20 模块的支持最完善, 而 GCC 和 Clang 尚处于完善阶段, 在实际开发中需要格外注意平台间的兼容性.

本文构建环境

本文中的例子均在 MSVC (Microsoft Visual Studio 2022 Preview) 上测试通过.
尚未在 Clang 和 GCC 上全面验证, 请根据实际需要选择合适的编译器和工具链.

样例GCC 14.2Clang 18MSVC 2022
简单示例✔✔✔
复杂示例✘✔✔
模块接口✘✘✔
子模块✘✔✔
模块分区✘✔✔

源码链接.


模块基础

模块声明

// global module fragment
module;

// 传统头文件, 比如
#include 
#include 

// module declaration; starts the module purview
export module module_name;

// 导入其他模块
import std.core;

// 本模块可见的变量, 函数, 类等
int local_var = 42;

// 导出的命名空间
export namespace sample {

// 导出的名字
export constexpr int foo = 42;
export void bar();
export class Baz{};
}  // namespace sample
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

export命令

有三种方式使用export:

导出声明
export module name;

export int foo(int fir, int sec);

export void bar();
  • 1
  • 2
  • 3
  • 4
  • 5
导出组
export module name;

export {
  int foo(int fir, int sec);
  void bar();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
导出命名空间
export module name;

export namespace name {
int foo(int fir, int sec);
void bar();
}  // namespace name
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

简单示例

模块文件 simple.ixx

export module simple;  // 模块声明

// 该函数将被导出
export int add(int a, int b) { return a + b; }

  • 1
  • 2
  • 3
  • 4
  • 5

export module simple; 是一个模块声明, 它声明了一个模块, 模块名为simple.

在模块中, 我们可以使用export关键字导出函数, 命名空间, 类, 变量等.
这样的实体可被其他模块导入.

主程序 simple_main.cpp

在使用的时候, 使用import simple;导入模块.

import simple; // 导入模块
#include 

int main() {
  std::cout << "simple add(1, 2)=" << add(1, 2) << "\n";
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

CMakeList.txt

add_library(simple_module)
target_sources(simple_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        simple.ixx
)
add_executable(simple_demo simple_main.cpp)
target_link_libraries(simple_demo simple_module)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

复杂示例

模块文件

请注意如果模块需要引入传统头文件, 需要使用如下写法.

module;

#include 
#include 

export module complex;

export namespace complex {
class Person {
 public:
  Person(unsigned age, std::string name) : age_(age), name_(std::move(name)) {}

  [[nodiscard]] std::string Name() const { return name_; }
  [[nodiscard]] unsigned Age() const { return age_; }

 private:
  unsigned age_;
  std::string name_;
};
}  // namespace complex
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

程序主文件

import complex;
#include 

int main() {
  complex::Person joy(18, "Joy");
  std::cout << "Name: " << joy.Name() << "\n";
  std::cout << "Age : " << joy.Age() << "\n";
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

cmake 设置

add_library(complex_module)
target_sources(complex_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        complex.ixx
)
add_executable(complex_demo complex_main.cpp)
target_link_libraries(complex_demo complex_module)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

模块接口

当模块变得越来越大时, 可能需要把模块的接口和实现分开. 这样可以更好地组织代码, 并且提高代码的可读性.

模块接口单元

module;

#include 

export module demo;

export namespace demo {

int sum(const std::vector<int>& v);

int prod(const std::vector<int>& v);
}  // namespace demo
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

模块实现单元

module demo;

#include 
#include 

namespace demo {
int sum(const std::vector<int>& v) {
  return std::accumulate(v.begin(), v.end(), 1, std::plus{});
}

int prod(const std::vector<int>& v) {
  return std::accumulate(v.begin(), v.end(), 1, std::multiplies{});
}
}  // namespace demo
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

主程序

import demo;

#include 
#include 

int main() {
  std::vector vec{1, 2, 3, 4, 5};
  std::cout << "sum  : " << demo::sum(vec) << std::endl;
  std::cout << "prod : " << demo::prod(vec) << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

cmake 设置如下:

add_library(separate_module)
target_sources(separate_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        separate_interface.ixx
)
target_sources(separate_module PUBLIC separate_impl.ixx)

add_executable(separate separate_main.cpp)
target_link_libraries(separate separate_module)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

子模块

对于大的模块可以将其分割为多个子模块. 然后设置一个主模块文件, 导入并导出子模块.

这里的每一个子模块都是一个独立的模块, 可以单独存在.

模块主文件 sort.ixx

设想一下我们目前有一个排序算法库, 包含几种不同的排序算法, 每种排序算法都可以独立使用.

module;

export module sort;
export import sort.bubble_sort;
export import sort.insert;
export import sort.quick;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

子模块文件

module;

#include 
#include 

export module sort.bubble_sort;

export namespace sort {
void bubble_sort(std::vector<int>& arr) {
  if (arr.size() < 2) return;
  for (int i = 0; i < arr.size() - 1; i++) {
    auto swapped = false;
    for (int j = 1; j < arr.size() - i; j++) {
      if (arr[j - 1] > arr[j]) {
        std::swap(arr[j - 1], arr[j]);
        swapped = true;
      }
    }
    if (!swapped) break;
  }
}
}  // namespace sort
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
module;

#include 

export module sort.insert;

export namespace sort {

void insertion_sort(std::vector<int>& arr) {
  for (int i = 1; i < arr.size(); i++) {
    auto k = arr[i];
    int j = i - 1;
    while (j >= 0 && arr[j] > k) {
      arr[j + 1] = arr[j];
      j--;
    }
    arr[j + 1] = k;
  }
}

}  // namespace sort
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

程序主文件

import sort;
#include 
#include 
#include 

int main() {
  std::vector v{5, 4, 3, 2, 1};
  sort::bubble_sort(v);
  std::cout << std::boolalpha << std::is_sorted(v.begin(), v.end(), std::less{})
            << std::endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

cmake 设置

add_library(sort_module)
target_sources(sort_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        sort.ixx
        sort_bubble.ixx
        sort_quick.ixx
        sort_insert.ixx
)
add_executable(sort_module_demo sort_main.cpp)
target_link_libraries(sort_module_demo sort_module)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

模块分区

模块分区与子模块类似, 唯一的区别是分区模块不能作为一个独立模块存在.

模块主文件

module;

export module partition;
export import :part1;
export import :part2;
  • 1
  • 2
  • 3
  • 4
  • 5

子分区文件

module;

#include 
#include 

export module partition:part1;

export namespace sort {
void bubble_sort(std::vector<int>& arr) {
  if (arr.size() < 2) return;
  for (int i = 0; i < arr.size() - 1; i++) {
    auto swapped = false;
    for (int j = 1; j < arr.size() - i; j++) {
      if (arr[j - 1] > arr[j]) {
        std::swap(arr[j - 1], arr[j]);
        swapped = true;
      }
    }
    if (!swapped) break;
  }
}
}  // namespace sort
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
module;

#include 

export module partition:part2;

export namespace sort {

void insertion_sort(std::vector<int>& arr) {
  for (int i = 1; i < arr.size(); i++) {
    auto k = arr[i];
    int j = i - 1;
    while (j >= 0 && arr[j] > k) {
      arr[j + 1] = arr[j];
      j--;
    }
    arr[j + 1] = k;
  }
}

}  // namespace sort
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

cmake 设置

add_library(partition_module)
target_sources(partition_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        partition.ixx
        partition_part1.ixx
        partition_part2.ixx
)
add_executable(partition_module_demo partition_main.cpp)
target_link_libraries(partition_module_demo partition_module)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

总结

  • 模块是 C++20 的新特性, 它可以解决头文件的低效问题. 导入模块的代价非常低, 并且导入顺序不重要.
    模块还可以解决名称冲突问题.

  • 模块由模块接口单元和模块实现单元组成. 必须有一个导出模块声明的模块接口单元, 以及任意多个模块实现单元.
    在模块接口中没有导出的名称具有模块链接, 不能在模块外部使用.

  • 模块可以有头文件, 也可以导入和重新导出其它模块.

  • C++20 标准库没有模块化. 使用 C++20 构建模块化的软件系统是一项具有挑战性的任务.

  • 为了构建大型软件系统, 模块提供了两种方法: 子模块和模块分区. 与分区不同, 子模块可以独立存在.

  • 由于头文件单元的存在, 可以用导入语句替换包含语句, 编译器会自动生成一个模块.

参考链接

  • CMake Support for C++ Modules
  • 源码链接
注:本文转载自blog.csdn.net的程序员阿荣的文章"https://blog.csdn.net/arong_xu/article/details/137088783"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top