当前位置: 首页 > article >正文

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).

示例代码

#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关键字作为占位符. 这一特性允许你编写更加通用和灵活的函数, 而不需要指定具体的参数类型.

#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来实现, 但现在我们可以直接使用概念来简化这一过程.

#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 的例子, 展示了如何使用这些新特性来过滤和转换数据:

#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. 灵活性: 可以表示固定大小或动态大小的序列.
#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
#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 : 用于定义必须在编译时求值的函数.
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 可以处理不同类型的参数.

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

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

#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 来支持自定义类型的格式化输出.
#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. 日历运算: 支持日期的加减运算, 如计算两个日期之间的天数.
#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:

#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;
}

输出

Working...
Working...
Working...
Stopped by request.

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

13. 并发特性

latchesbarriers

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

  • 构造函数: 接受一个计数器, 表示需要等待的线程数量.
  • count_down(): 递减计数器.
  • wait(): 阻塞当前线程, 直到计数器归零.
#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(): 递减计数器并阻塞当前线程, 直到所有线程到达.
#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 个线程可以同时工作, 其余线程会被阻塞, 直到有其他线程释放信号量.
#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, 表示资源不可用.
  • 工作者线程尝试获取信号量, 但会被阻塞, 直到生产者线程释放信号量, 表示资源可用.
#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 对象被销毁时, 缓冲区的内容会自动刷新到目标输出流.
#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” >}}

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” >}}

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” >}}

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()

std::string str = "Hello, World!";

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

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

受限的 string 成员函数 reserve()

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

新的工具类函数ssize()

// 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

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

#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 的代码段等特殊格式可能会被移除, 感兴趣的读者可以访问我的原贴.

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


http://www.kler.cn/a/581848.html

相关文章:

  • AWS原生架构下的服务器性能与成本平衡之道——海外业务云端实践
  • 用Python实现链上数据爬取与分析
  • RISC-V特权模式与寄存器
  • MATLAB 控制系统设计与仿真 - 22
  • 从零开始用AI开发游戏(一)
  • 关于在electron(Nodejs)中使用 Napi 的简单记录
  • 【GPT入门】第6课 openai接口介绍与参数说明
  • 远程手机遥控开关原理及应用
  • git commit messege 模板设置 (规范化管理git)
  • 机器学习基础(4)
  • 清华同方国产电脑能改windows吗_清华同方国产系统改win7教程
  • 《PaddleOCR》—— OCR
  • Agentic RAG 详解 - 从头开始​​构建你自己的智能体系统
  • 【web】网页崩溃
  • Cannot resolve symbol ‘view‘ Androidstudio报错解决办法
  • json中文编码问题
  • python之左移右移位运算
  • 政安晨【零基础玩转各类开源AI项目】Wan 2.1 本地部署,基于ComfyUI运行,最强文生视频 图生视频,一键生成高质量影片
  • Python+DeepSeek:开启AI编程新次元——从自动化到智能创造的实战指南
  • 【LLM学习】1-NLP回顾+Pytorch复习