C++20 新特性总结

简要总结

C++20 引入了四项非常大的更新, 分别是:

  1. 概念(Concepts). 用来简化模板编程, 强化表达能力. 并且使得出错原因更容易查找.
  2. 模块(Modules). 这是代码组织方面非常大的更新. 提供了新的方式来组织代码, 并且可以减少编译时间.
  3. 范围库(Ranges and Views). 轻量级的, 非拥有的范围库, 允许对数据进行各种操作.
  4. 协程(Coroutine). 多线程编程方面的一次重大更新.

本文将会对 C++20 的新特性做一个简要总结, 方便读者快速了解.

1. 三路比较运算符 <=>

C++20 引入了一个新的运算符 <=>, 称为 "三路比较运算符""spaceship 运算符" . 这个运算符用于同时执行小于, 等于和大于的比较操作, 并返回一个特殊类型的值, 该类型表示两个对象之间的关系. 具体来说, 它会返回一个 std::strong_ordering, std::weak_ordering, 或者 std::partial_ordering 类型的对象, 分别对应强有序, 弱有序以及部分有序的情况.

  • 当左侧小于右侧时, 返回一个小于零的值(通常是std::strong_ordering::less).
  • 当两侧相等时, 返回零(std::strong_ordering::equal).
  • 当左侧大于右侧时, 返回一个大于零的值(std::strong_ordering::greater).

示例代码

cpp 复制代码
#include <compare>
#include <iostream>

struct Point {
  int x, y;

  auto operator<=>(const Point&) const = default;
};

int main() {
  Point a{1, 2}, b{1, 2}, c{2, 3};

  if (a <=> b == 0) {
    std::cout << "a and b are equal\n";
  }

  if ((a <=> c) < 0) {
    std::cout << "a is less than c\n";
  }

  return 0;
}

在这个示例中, Point 结构体通过默认的方式实现了三路比较运算符, 这意味着编译器会自动生成所有基于成员变量的比较逻辑.

进一步阅读: C++20 Spaceship 操作符 ('<=>'): 现代 C++ 的比较利器

2. 函数参数支持占位符类型

C++20 支持在函数参数中使用auto关键字作为占位符. 这一特性允许你编写更加通用和灵活的函数, 而不需要指定具体的参数类型.

cpp 复制代码
#include <iostream>

auto add(auto a, auto b) { return a + b; }

int main() {
  std::cout << "Integers: " << add(1, 2) << "\n";     // 输出3
  std::cout << "Doubles: " << add(1.5, 2.5) << "\n";  // 输出4.0

  return 0;
}

3. 概念和要求

  • 概念(Concepts): 是用于描述模板参数类型必须满足的一组要求的实体. 它们可以用来约束模板参数, 确保只有满足特定条件的类型才能被用作模板参数.

  • 要求(Requirements): 通常指模板参数需要满足的具体条件或行为, 比如支持某种操作符, 拥有特定成员函数等.

假设我们想要定义一个函数模板, 该模板只接受那些可以进行比较操作(例如<)的类型. 在 C++20 之前, 这可能需要使用 SFINAE 或者static_assert来实现, 但现在我们可以直接使用概念来简化这一过程.

cpp 复制代码
#include <concepts>
#include <string>

// 定义一个名为Sortable的概念, 要求类型T支持小于运算符
template <typename T>
concept Sortable = requires(T a, T b) {
  { a < b } -> std::convertible_to<bool>;
};

// 仅对Sortable类型的参数有效
void sortFunction(Sortable auto& container) {
  // 假设这里实现了排序逻辑
}

struct MyType {
  int value;
  bool operator<(const MyType& other) const { return value < other.value; }
};

int main() {
  int arr[] = {1, 3, 2};
  sortFunction(arr);  // 正确, int支持<运算

  MyType myArr[] = {{1}, {3}, {2}};
  sortFunction(myArr);  // 正确, MyType也重载了<运算符

  return 0;
}

关键点解析

  • 概念定义 : 通过concept关键字定义概念, 然后使用requires子句列出类型必须满足的要求. 在这个例子中, Sortable概念要求类型支持<运算, 并且结果可以转换为bool.

  • 应用概念 : 在模板声明中使用概念作为约束, 如Sortable auto&, 这样就限制了传入的容器元素类型必须满足Sortable概念.

使用概念不仅使代码更易于理解, 而且当传入不符合概念要求的类型时, 编译器能提供更有意义的错误消息, 从而大大提高了开发效率. 此外, 概念还支持复杂的组合和继承关系, 使得构建复杂的需求系统成为可能.

进一步阅读: C++20 Concepts 简介

4. ranges/views 库

C++20 引入了RangesViews, 它们是标准库的一部分, 旨在简化容器, 数组和其他序列数据的操作. 通过 Ranges 和 Views, 可以更直观地处理数据序列, 并支持函数式编程风格. 它们提供了强大的工具来创建, 组合以及操作数据流, 而无需手动迭代或复制数据.

Ranges是 C++20 中的一个新概念, 它扩展了 STL(标准模板库)的容器和算法, 使得操作序列更加直观和高效. 一个 Range 是一个可以遍历的数据序列, 它可以是有限的也可以是无限的. Ranges 的核心思想是将算法与容器解耦, 允许以声明式的方式定义数据处理流程.

Views是轻量级, 非拥有的范围, 它们提供了一种方式来查看原始数据的一种特定视角, 而不实际拥有数据. 这意味着 views 不会复制数据, 而是基于原始数据进行计算, 这使得它们非常高效且节省内存.

以下是一个使用 C++20 Ranges 和 Views 的例子, 展示了如何使用这些新特性来过滤和转换数据:

cpp 复制代码
#include <fmt/ranges.h>

#include <algorithm>
#include <iostream>
#include <ranges>
#include <vector>

int main() {
  std::vector<int> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

  // 使用views过滤出偶数, 并对每个元素加1
  auto result = vec | std::views::filter([](int n) { return n % 2 == 0; }) |
                std::views::transform([](int n) { return n * n; });

  // 打印结果
  fmt::println("{}", result);
  // 输出: [0, 4, 16, 36, 64]

  return 0;
}

关键点解析

  • 管道操作符(|) : Ranges 和 Views 支持使用管道操作符来链式调用各种视图操作, 如过滤(filter), 转换(transform)等, 使得代码更加简洁和易读.

  • Filter 和 Transform : 在这个例子中, 我们首先使用std::views::filter来筛选出所有偶数, 然后使用std::views::transform对每个筛选出的数字加 1. 这两个操作都是惰性求值的, 也就是说, 它们只有在真正需要时才会执行相应的计算.

  • 性能优势: 由于 views 不复制数据, 它们可以在处理大量数据时提供显著的性能优势. 此外, 由于它们的惰性求值特性, 只有当真正访问数据时才会进行计算, 这也进一步提高了效率.

进一步阅读: Modern C++ Ranges/View 库简介

5. std::span

std::span 是一个轻量级, 非拥有的视图, 用于表示连续内存序列(如数组, std::vector 等). 它不拥有数据, 而是提供对数据的引用, 因此避免了不必要的复制, 同时提供了对序列的安全访问.

主要特点:

  1. 非拥有 : std::span 不管理内存, 仅作为数据的视图.
  2. 连续内存 : 适用于连续内存的数据结构, 如数组, std::vector 等.
  3. 安全性: 支持边界检查, 避免越界访问.
  4. 灵活性: 可以表示固定大小或动态大小的序列.
cpp 复制代码
#include <fmt/ranges.h>

#include <span>
#include <vector>

void print(std::span<int> span) { fmt::println("{}", span); }

int main() {
  // 使用 std::span 查看数组
  int arr[] = {1, 2, 3, 4, 5};
  print(arr);  // 输出: 1 2 3 4 5

  // 使用 std::span 查看 vector
  std::vector<int> vec = {6, 7, 8, 9, 10};
  print(vec);  // 输出: 6 7 8 9 10

  // 使用 std::span 查看部分数据
  std::span<int> partial(vec.data() + 1, 3);
  print(partial);  // 输出: 7 8 9

  return 0;
}

std::span 是处理连续内存序列的强大工具, 尤其适用于需要高效传递和操作数据的场景.

进一步阅读: {{< PostRef path="/posts/2024-07-31-cpp20-span.md" >}}

6. 类型模板参数扩展

C++20 对非类型模板参数(Non-Type Template Parameters, NTTP)进行了扩展, 允许使用更多类型的非类型模板参数. 在 C++17 及之前, 非类型模板参数只能是整型, 指针, 引用或枚举类型. C++20 扩展了这一能力,

NTTP 支持如下类型:

  1. 浮点类型
  2. 结构体和简单类
  3. Lambda
cpp 复制代码
#include <iostream>
#include <string_view>

// 使用浮点类型作为非类型模板参数
template <double Value>
void print() {
  std::cout << "Double value: " << Value << "\n";
}

int main() {
  // 浮点类型模板参数
  print<3.14>();  // 输出: Double value: 3.14

  return 0;
}

优势:

  • 更强的表达能力: 支持更多类型的非类型模板参数, 使得模板编程更加灵活.
  • 编译时计算 : 结合 constexpr 和模板元编程, 可以在编译时进行更复杂的计算和优化.

C++20 的非类型模板参数扩展为模板编程带来了更多可能性, 尤其是在需要编译时处理浮点数或字符串的场景中非常有用.

7. 编译时计算

C++20 在编译时计算(Compile-Time Computing)方面引入了一些新特性, 使得在编译时进行复杂的计算和优化变得更加方便.

  1. constexpr 增强: C++20 进一步扩展了 constexpr 的使用范围, 允许在编译时执行更多的操作, 包括动态内存分配, 异常处理等.
  2. constinit: 表示一个全局变量或常 static 类型变量在编译时完成初始化. constinit = constexpr - const. 注意constinit修饰的变量是允许后续修改内容的.
  3. consteval : 用于定义必须在编译时求值的函数.
cpp 复制代码
constexpr int square(int x) { return x * x; }
consteval int triple(int x) { return x * 3; }

int main() {
  constexpr int CX_VAL = square(5);  // 编译期计算
  // CX_VAL = 1;                     // 错误, 常量不可修改
  int val = square(5);  // 初始化普通变量
  square(val);          // OK, 可以在运行时计算

  constinit static int value = 10;  // 在编译期初始化, 但不是常量
  value = 20;
  // constinit int value2 = 10; // 错误, constinit 只能用于静态变量或者全局变量

  const int data = triple(4);  // 合法, 因为在编译期求值
  // triple(runtime);          // 错误, 不能在在运行期求值

  return 0;
}

进一步阅读: C++ constexpr, consteval 和 constinit 简要介绍

8. lambda 扩展

C++20 对 Lambda 表达式进行了一些扩展, 使得 Lambda 更加灵活和强大. 以下是 C++20 中 Lambda 表达式的主要扩展特性:

  1. 模板 Lambda: C++20 允许在 Lambda 表达式中使用模板参数, 使得 Lambda 可以处理不同类型的参数.

    cpp 复制代码
    auto genericLambda = []<typename T>(T a, T b) {
      // logic here
    };
  2. this 捕获: C++20 引入了 [*this] 捕获方式, 允许在 Lambda 中捕获当前对象的副本, 而不是引用.

  3. constevalconstexpr Lambda: Lambda 表达式现在可以标记为 constevalconstexpr, 以便在编译时求值.

cpp 复制代码
#include <fmt/core.h>
#include <fmt/ranges.h>

int main() {
  // 1. 泛型 Lambda
  auto generic = []<typename T>(T a, T b) { return a + b; };

  fmt::println("Generic Lambda (int): {}", generic(1, 2));         // 输出: 3
  fmt::println("Generic Lambda (double): {}", generic(1.5, 2.5));  // 输出: 4.0

  // 2. `constexpr` Lambda
  constexpr auto constexprLambda = [](int a, int b) constexpr { return a + b; };

  constexpr int result = constexprLambda(3, 4);
  fmt::println("Constexpr Lambda: {}", result);  // 输出: 7

  // 3. `[*this]` 捕获
  struct MyStruct {
    int value = 42;
    auto getLambda() {
      return [*this]() { return value; };
    }
  };

  MyStruct obj;
  auto lambda = obj.getLambda();
  obj.value = 100;
  fmt::println("Lambda with [*this] capture: {}", lambda());  // 输出: 42

  return 0;
}

9. 格式化输出

C++20 引入了新的格式化输出库 std::format, 它提供了一种类型安全且更灵活的字符串格式化方式, 类似于 Python 的 str.format(). std::format 通过使用格式化字符串和占位符来生成格式化的输出, 避免了传统 printf 风格函数中的类型不安全问题.

  1. 类型安全 : std::format 是类型安全的, 编译器会在编译时检查格式字符串和参数的类型是否匹配.
  2. 简洁易读 : 格式化字符串使用 {} 作为占位符, 语法简洁且易于理解.
  3. 支持自定义类型 : 可以通过重载 std::formatter 来支持自定义类型的格式化输出.
cpp 复制代码
#include <format>
#include <iostream>

int main() {
  int num = 42;
  double pi = 3.14159;
  std::string name = "Alice";

  // 基本格式化
  std::cout << std::format("Number: {}, Pi: {:.2f}, Name: {}\n", num, pi, name);
  // 输出: Number: 42, Pi: 3.14, Name: Alice

  // 格式化字符串存储到变量
  std::string formatted =
      std::format("Number: {}, Pi: {:.2f}, Name: {}", num, pi, name);
  std::cout << formatted << std::endl;
  // 输出: Number: 42, Pi: 3.14, Name: Alice

  // 格式化字符串中的位置参数
  std::cout << std::format("Name: {2}, Pi: {1:.2f}, Number: {0}\n", num, pi,
                           name);
  // 输出: Name: Alice, Pi: 3.14, Number: 42

  return 0;
}

进一步阅读: C++23 格式化输出新特性详解: std::print 和 std::println

10. chrono 新增日期和时区

C++20 在 <chrono> 库中引入了对日期, 时间和时区的支持, 使得处理日期和时间变得更加方便和强大. 新的特性包括对日历日期的支持, 时区转换以及时间点之间的计算等.

  1. 日历日期支持 : C++20 引入了 year, month, day 等类型, 用于表示日历日期.
  2. 时间点扩展 : sys_timelocal_time 分别表示系统时间和本地时间.
  3. 时区支持 : time_zonezoned_time 用于处理时区转换.
  4. 日历运算: 支持日期的加减运算, 如计算两个日期之间的天数.
cpp 复制代码
#include <chrono>
#include <format>
#include <iostream>

int main() {
  using namespace std::chrono;

  // 1. 创建日历日期
  year_month_day today = 2025y / March / 6d;
  std::cout << "Today is: " << today << std::endl;  // 输出: 2025-03-06

  // 2. 创建时间点
  sys_time<seconds> sys_now = time_point_cast<seconds>(system_clock::now());
  std::cout << "System time now: " << sys_now << std::endl;  // 输出当前系统时间

  // 3. 时区转换
  auto utc_time = sys_now;
  auto local_time = zoned_time{current_zone(), utc_time};
  std::cout << "Local time now: " << local_time
            << std::endl;  // 输出当前本地时间

  // 4. 计算两个日期之间的天数
  year_month_day next_week = sys_days{today} + days{7};
  std::cout << "Next week is: " << next_week << std::endl;  // 输出: 2025-03-13

  // 5. 格式化输出
  std::cout << std::format("Today is {:%Y-%m-%d}\n",
                           today);  // 输出: Today is 2025-03-06

  return 0;
}

进一步阅读: 一文读懂 C++ chrono 库: duration, clocks, date, timezone

11. 协程

C++20 的协程设计比较复杂, 可以参考以下链接:
Modern C++ Coroutine 简介

12. std::jthreadstop_token

C++20 引入了 std::jthreadstd::stop_token, 它们为多线程编程提供了更安全和更方便的线程管理机制. std::jthreadstd::thread 的增强版本, 支持在离开作用域时自动join, 而 std::stop_token 提供了一种机制来请求线程停止执行.

  1. std::jthread:

    • 自动管理线程的生命周期, 析构时会自动调用 join(), 确保线程在销毁前完成执行.
    • 支持 std::stop_token, 允许线程在外部请求停止执行.
  2. std::stop_tokenstd::stop_source:

    • std::stop_token 用于检查是否收到停止请求.
    • std::stop_source 用于发出停止请求.
    • 两者可以配合使用, 实现线程的优雅停止.

以下是一个简单的示例, 展示了如何使用 std::jthreadstd::stop_token:

cpp 复制代码
#include <iostream>
#include <thread>
#include <stop_token>
#include <chrono>

void worker(std::stop_token stopToken) {
    while (!stopToken.stop_requested()) {
        std::cout << "Working...\n";
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Stopped by request.\n";
}

int main() {
    // 创建一个 jthread, 并传递 worker 函数
    std::jthread t(worker);

    // 主线程等待 3 秒
    std::this_thread::sleep_for(std::chrono::seconds(3));

    // 请求线程停止
    t.request_stop();

    // jthread 析构时会自动 join, 确保线程完成执行
    return 0;
}

输出

plaintext 复制代码
Working...
Working...
Working...
Stopped by request.

进一步阅读: C++20 std::jthread 完全指南 - 简化多线程编程与线程管理

13. 并发特性

latchesbarriers

std::latch 是一种同步原语, 用于确保一组线程在继续执行之前等待某个操作完成. std::latch 一旦倒数到零, 就不能重置. 它非常适合用于等待一组线程完成初始化或其他初始化操作.

  • 构造函数: 接受一个计数器, 表示需要等待的线程数量.
  • count_down(): 递减计数器.
  • wait(): 阻塞当前线程, 直到计数器归零.
cpp 复制代码
#include <iostream>
#include <latch>
#include <thread>
#include <vector>

void worker(std::latch& latch) {
  // 模拟工作
  std::this_thread::sleep_for(std::chrono::seconds(1));
  std::cout << "Worker thread done\n";
  latch.count_down();
}

int main() {
  std::latch latch(3);  // 等待3个线程

  std::vector<std::thread> threads;
  for (int i = 0; i < 3; ++i) {
    threads.emplace_back(worker, std::ref(latch));
  }

  latch.wait();  // 主线程等待所有工作线程完成
  std::cout << "All worker threads are done\n";

  for (auto& t : threads) {
    t.join();
  }

  return 0;
}

Barriers (栅栏)

std::barrier 类似于 std::latch, 但它可以被重置. std::barrier 用于一组线程在继续执行之前等待所有线程到达某个同步点. 所有线程到达后, 它们可以继续执行, 而 std::barrier 也可以被重置以便再次使用.

  • 构造函数: 接受一个计数器, 表示需要等待的线程数量.
  • arrive_and_wait(): 递减计数器并阻塞当前线程, 直到所有线程到达.
  • arrive_and_drop(): 递减计数器但不等待其他线程.
  • arrive_and_wait(): 递减计数器并阻塞当前线程, 直到所有线程到达.
cpp 复制代码
#include <barrier>
#include <iostream>
#include <thread>
#include <vector>

void worker(std::barrier<>& barrier, int id) {
  std::cout << "Worker " << id << " is ready\n";
  barrier.arrive_and_wait();  // 等待所有线程到达
  std::cout << "Worker " << id << " is processing\n";
  barrier.arrive_and_wait();  // 等待所有线程完成处理
  std::cout << "Worker " << id << " is done\n";
}

int main() {
  std::barrier barrier(3);  // 等待3个线程

  std::vector<std::thread> threads;
  for (int i = 0; i < 3; ++i) {
    threads.emplace_back(worker, std::ref(barrier), i);
  }

  for (auto& t : threads) {
    t.join();
  }

  return 0;
}

更多阅读: C++ Latch 和 Barrier: 新手指南

Semaphores

C++20 引入了 std::counting_semaphorestd::binary_semaphore 作为同步原语, 用于多线程编程中的资源管理. 信号量(Semaphore)是一种用于控制多个线程对共享资源访问的机制. 信号量维护一个计数器, 该计数器表示可用资源的数量. 当一个线程需要访问资源时, 它会尝试获取信号量; 如果计数器大于零, 则线程可以访问资源, 并将计数器减一; 如果计数器为零, 则线程会被阻塞, 直到有其他线程释放资源.

  • std::counting_semaphore: 用于管理多个资源的访问. 构造函数接受一个初始计数值, 表示可用资源的数量.
  • std::binary_semaphore: 特殊的计数信号量, 等价于 std::counting_semaphore<1>, 用于二进制信号量的场景.
  • acquire(): 尝试获取信号量, 如果计数器大于零, 则计数器减一; 否则, 线程会被阻塞.
  • try_acquire(): 尝试获取信号量, 如果计数器大于零, 则计数器减一并返回 true; 否则, 立即返回 false.
  • release(): 释放信号量, 将计数器加一, 唤醒一个等待的线程(如果有).
使用 std::counting_semaphore
  • 创建了一个 std::counting_semaphore, 初始计数为 5, 表示有 5 个可用资源.
  • 10 个线程尝试获取信号量, 但只有 5 个线程可以同时工作, 其余线程会被阻塞, 直到有其他线程释放信号量.
cpp 复制代码
#include <iostream>
#include <semaphore>
#include <thread>
#include <vector>

std::counting_semaphore<5> sem(5);  // 指定模板参数类型为 int

void worker(int id) {
  sem.acquire();  // 尝试获取信号量
  std::cout << "Worker " << id << " is working\n";
  std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟工作
  std::cout << "Worker " << id << " is done\n";
  sem.release();  // 释放信号量
}

int main() {
  std::vector<std::thread> threads;
  for (int i = 0; i < 10; ++i) {
    threads.emplace_back(worker, i);
  }

  for (auto& t : threads) {
    t.join();
  }

  return 0;
}
使用 std::binary_semaphore
  • 创建了一个 std::binary_semaphore, 初始计数为 0, 表示资源不可用.
  • 工作者线程尝试获取信号量, 但会被阻塞, 直到生产者线程释放信号量, 表示资源可用.
cpp 复制代码
#include <iostream>
#include <semaphore>
#include <thread>
#include <vector>

std::binary_semaphore sem(0);  // 初始状态为0, 表示资源不可用

void worker(int id) {
  sem.acquire();  // 尝试获取信号量
  std::cout << "Worker " << id << " is working\n";
  std::this_thread::sleep_for(std::chrono::seconds(1));  // 模拟工作
  std::cout << "Worker " << id << " is done\n";
}

void producer() {
  std::this_thread::sleep_for(std::chrono::seconds(2));  // 模拟生产时间
  std::cout << "Producer is ready\n";
  sem.release();  // 释放信号量, 表示资源可用
}

int main() {
  std::thread t1(worker, 1);
  std::thread t2(producer);

  t1.join();
  t2.join();

  return 0;
}

更多阅读: 条件变量 vs 信号量: 如何选择适合你的多线程同步工具?

Synchronized Output Streams

C++20 引入了 std::osyncstream 作为同步输出流, 用于确保在多线程环境中对输出流的写操作是线程安全的. std::osyncstream 是一个同步输出流缓冲区, 它将输出操作缓冲起来, 然后在析构时将缓冲区的内容原子地写入到目标输出流中. 这避免了多个线程同时写入输出流时可能出现的交错输出问题.

  • 线程安全 : std::osyncstream 确保对输出流的写操作是线程安全的.
  • 缓冲区 : 输出操作会被缓冲, 直到 std::osyncstream 对象被销毁或显式刷新.
  • 自动刷新 : 当 std::osyncstream 对象被销毁时, 缓冲区的内容会自动刷新到目标输出流.
cpp 复制代码
#include <iostream>
#include <syncstream>
#include <thread>
#include <vector>

void worker(std::ostream& os, int id) {
  std::osyncstream sync_os(os);
  sync_os << "Worker " << id << " is starting\n";
  std::this_thread::sleep_for(std::chrono::milliseconds(100));
  sync_os << "Worker " << id << " is done\n";
}

int main() {
  std::vector<std::thread> threads;
  for (int i = 0; i < 5; ++i) {
    threads.emplace_back(worker, std::ref(std::cout), i);
  }

  for (auto& t : threads) {
    t.join();
  }

  return 0;
}

输出示例

复制代码
Worker 1 is starting
Worker 1 is done
Worker 0 is starting
Worker 0 is done
Worker 4 is starting
Worker 4 is done
Worker 2 is starting
Worker 2 is done
Worker 3 is starting
Worker 3 is done

每个线程的输出都是完整的, 不会与其他线程的输出交错, 这得益于 std::osyncstream 的线程安全特性.

进一步阅读: C++20 std::osyncstream 完全指南 - 解决多线程输出混乱问题

14. Modules

C++20 引入了模块(Modules), 这是一种新的代码组织方式, 旨在替代传统的头文件(#include)机制. 模块提供了更好的编译性能, 更清晰的代码结构以及更强的封装性.

  1. 编译性能提升: 模块避免了头文件的重复解析, 从而显著减少了编译时间.
  2. 封装性增强: 模块可以控制哪些符号对外可见, 哪些符号仅在模块内部使用.
  3. 减少宏污染: 模块不会像头文件那样引入宏定义, 避免了宏污染问题.
  4. 简化依赖管理: 模块的依赖关系更加清晰, 减少了复杂的头文件包含顺序问题.

代码示例

以下是一个简单的模块示例, 展示了如何定义和使用模块:

{{< CodeTabs >}}

{{< Code title="math.ixx" >}}

cpp 复制代码
export module math;

export int add(int a, int b) { return a + b; }

export int multiply(int a, int b) { return a * b; }

{{< /Code >}}

{{< Code title="main.cpp" >}}

cpp 复制代码
import math;
#include <iostream>

int main() {
  std::cout << "Add: " << add(2, 3) << "\n";            // 输出: Add: 5
  std::cout << "Multiply: " << multiply(2, 3) << "\n";  // 输出: Multiply: 6
  return 0;
}

{{< /Code >}}

{{< Code title="CMakeLists.txt" >}}

cmake 复制代码
add_library(simple_module)
target_sources(simple_module
        PUBLIC
        FILE_SET CXX_MODULES FILES
        math.ixx
)
add_executable(simple_demo main.cpp)
target_link_libraries(simple_demo simple_module)

{{< /Code >}}

{{< /CodeTabs >}}

进一步阅读: CMake 构建 C++20 Module 实例(使用 MSVC)

其他改进

String Members starts_with() and ends_with()

cpp 复制代码
std::string str = "Hello, World!";

// 检查字符串是否以 "Hello" 开头
assert(str.starts_with("Hello"));

// 检查字符串是否以 '!' 结尾
assert(str.ends_with('!'));

受限的 string 成员函数 reserve()

对于 string 来说, 成员函数 reserve不能再用于请求缩小字符串的容量(内存分配给字符串的值).

新的工具类函数ssize()

cpp 复制代码
// int与size_t的比较会导致编译器警告
for (int i = 0; i < std::ssize(str); ++i) {
}

// 使用ssize, 得到有符号的类型
for (int i = 0; i < std::ssize(str); ++i) {
}

std::source_location

代码位置, 帮助追踪代码位置.

cpp 复制代码
#include <fmt/core.h>

#include <source_location>

int main() {
  auto sl = std::source_location::current();
  fmt::println("file: {}", sl.file_name());
  fmt::println("function: {}", sl.function_name());
  fmt::println("line/col: {}/{}", sl.line(), sl.column());

  return 0;
}

专栏目录

C++17 新特性总结
C++20 新特性总结
C++23 新特性总结
C++26 新特性预览(Preview)

说明

本专栏的文章同步发布在 CSDN 和我的个人网站上. 由于 CSDN 的排版限制, 诸如代码高亮, 链接到 Compiler Explorer 的代码段等特殊格式可能会被移除, 感兴趣的读者可以访问我的原贴.

此外, 文中包含许多指向我个人网站的链接, 逐一修正这些链接需要大量时间和精力, 暂时无法完成, 敬请谅解. 感谢您的理解与支持!

相关推荐
czhc11400756638 分钟前
Java117 最长公共前缀
java·数据结构·算法
java 乐山11 分钟前
蓝牙网关(备份)
linux·网络·算法
云泽80818 分钟前
快速排序算法详解:hoare、挖坑法、lomuto前后指针与非递归实现
算法·排序算法
数字化脑洞实验室19 分钟前
智能决策算法的核心原理是什么?
人工智能·算法·机器学习
流烟默19 分钟前
机器学习中拟合、欠拟合、过拟合是什么
人工智能·算法·机器学习
Brianna Home20 分钟前
现代C++:从性能泥潭到AI基石
开发语言·c++·算法
再卷也是菜21 分钟前
算法基础篇(10)递归型枚举与回溯剪枝
算法·深度优先·剪枝
吃着火锅x唱着歌28 分钟前
LeetCode 2016.增量元素之间的最大差值
数据结构·算法·leetcode
qq192572302735 分钟前
c++特性
开发语言·c++·算法
徐子童37 分钟前
FloodFill---BFS
算法·bfs·宽度优先·队列·floodfill