当前位置: 首页 > 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";     // 输出3std::cout << "Doubles: " << add(1.5, 2.5) << "\n";  // 输出4.0return 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过滤出偶数, 并对每个元素加1auto 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 查看 vectorstd::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 9return 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.14return 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. 泛型 Lambdaauto generic = []<typename T>(T a, T b) { return a + b; };fmt::println("Generic Lambda (int): {}", generic(1, 2));         // 输出: 3fmt::println("Generic Lambda (double): {}", generic(1.5, 2.5));  // 输出: 4.0// 2. `constexpr` Lambdaconstexpr 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());  // 输出: 42return 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: 42return 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-06return 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);  // 指定模板参数类型为 intvoid 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: 5std::cout << "Multiply: " << multiply(2, 3) << "\n";  // 输出: Multiply: 6return 0;
}

{{< /Code >}}

{{< Code title=“CMakeLists.txt” >}}

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

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

相关文章:

C++20 新特性总结

简要总结 C20 引入了四项非常大的更新, 分别是: 概念(Concepts). 用来简化模板编程, 强化表达能力. 并且使得出错原因更容易查找.模块(Modules). 这是代码组织方面非常大的更新. 提供了新的方式来组织代码, 并且可以减少编译时间.范围库(Ranges and Views). 轻量级的, 非拥有…...

软件设计模式之简单工厂模式

目录 一.类图&#xff08;手机生产&#xff09; 二.代码实现 Iphone类&#xff1a; Vivo类&#xff1a; Mobile类&#xff1a; MobileFactory类&#xff1a; Client类&#xff1a; 一.类图&#xff08;手机生产&#xff09; 补充&#xff1a;MobileFactory也可以直接指向…...

内网激活JRebel插件(无网络环境)

1.官网下载安装包,JRebel and XRebel JRebel and XRebel - IntelliJ IDEs Plugin | Marketplace 2.以IInstall Plugin from Disk的方式读取 3.运行JrebelServer.jar 终端输入&#xff1a; java -jar JrebelServer.jar -p 8080 (默认8080端口)服务会自动打开浏览器至 http:/…...

LiveGBS流媒体平台GB/T28181常见问题-视频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验

LiveGBS流媒体平台GB/T28181常见问题频流安全控制HTTP接口鉴权勾选流地址鉴权后401Unauthorized如何播放调用接口流地址校验&#xff1f; 1、安全控制1.1、HTTP接口鉴权1.2、流地址鉴权 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.…...

Java 大视界 -- 区块链赋能 Java 大数据:数据可信与价值流转(84)

&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

接口自动化入门 —— Http的请求头,请求体,响应码解析!

在接口自动化测试中&#xff0c;HTTP请求头、请求体和响应码是核心组成部分。理解它们的作用、格式和解析方法对于进行有效的接口测试至关重要。以下是详细解析&#xff1a; 1. HTTP 请求头&#xff08;Request Header&#xff09; 1.1 作用 请求头是客户端向服务器发送的附加…...

upload-labs(1-20)详解(专业版)

目录 第1关 第2关 第3关 第4题 第5题 第6题 第7题 第8题 第9题 第10题 第11题 第12题 第13题 第1关 查看源码 在第一关是一个前端js的一个后缀识别&#xff1a;当不为jpg、png、gif时候出现弹窗 检查源码 将return checkFile() 改为 return ture 就可以将php顺利…...

Linux 生成静态库

文章目录 前提小知识生成和使用.a库操作步骤 在应用程序中&#xff0c;有一些公共的代码需要反复使用的&#xff0c;可以把这些代码制作成“库文件”&#xff1b;在链接的步骤中&#xff0c;可以让链接器在“库文件”提取到我们需要使用到的代码&#xff0c;复制到生成的可执行…...

ARMV8的64位指令

一、介绍 ARMv8 体系结构最大的改变是增加了一个新的 64 位的指令集&#xff0c;这是早前 ARM 指令集 的有益补充和增强。它可以处理 64 位宽的寄存器和数据并且使用 64 位的指针来访问内存。这 个新的指令集称为 A64 指令集&#xff0c;运行在 AArch64 状态。 ARMv8 兼容旧的…...

【git】 贮藏 stash

贮藏是我在sourcetree上看到的名词。之前只是浅浅的用来收藏一下修改的文件&#xff0c;没有完整的使用过。今天有幸使用了一次就来展开说说。 使用原因就不赘述了&#xff0c;错误的操作少提为好&#xff0c;操作步骤如下&#xff1a; 查看贮藏列表git stash list #输出&…...

ctf-web: php原生类利用 -- GHCTF Popppppp

源代码 <?php error_reporting(0); class CherryBlossom { public $fruit1; public $fruit2; public function __construct($a) {$this->fruit1 $a; } function __destruct() { echo $this->fruit1; } public function __toString() { $newFunc …...

PawSQL for TDSQL:腾讯云TDSQL数据库性能优化全攻略

TDSQL 作为腾讯云推出的分布式数据库&#xff0c;凭借其高扩展性、高可用性和高性能等优势&#xff0c;广泛应用于金融、互联网、政务等领域。随着业务的不断增长和数据量的爆炸式增长&#xff0c;如何优化 TDSQL 数据库的性能&#xff0c;成为众多企业和开发者面临的挑战。本文…...

202250311-WINDOWS本地4G显存Docker运行vLLM

前置&#xff1a; 需要去huggingface注册账号获取token&#xff1a;HUGGING_FACE_HUB_TOKEN 运行vLLM docker run --name LocalvLLM_qwen1.5B_Int4 --runtime nvidia --gpus all -v D:/vLLM/.cache/huggingface:/root/.cache/huggingface --env "HUGGING_FAC…...

Scala 中生成一个RDD的方法

在 Scala 中&#xff0c;生成 RDD&#xff08;弹性分布式数据集&#xff09;的主要方法是通过 SparkContext&#xff08;或 SparkSession&#xff09;提供的 API。以下是生成 RDD 的常见方法&#xff1a; 1. 从本地集合创建 RDD 使用 parallelize 方法将本地集合&#xff08;如…...

T-SQL 语言基础:表运算符与联接

目录 介绍表运算符概述交叉联接内联接外联接联接实例总结引用 1. 介绍 在这篇博客中&#xff0c;主要涉及 T-SQL 中的表运算符与联接。联接操作是 SQL 查询中最常用的操作之一&#xff0c;它允许我们在多个表之间进行数据关联。通过了解不同类型的联接及其应用场景&#xff…...

Electron打包工具对比

在 Electron 生态中&#xff0c;打包工具的选择直接影响开发效率、配置复杂度和最终应用的性能。以下是主流的 Electron 打包工具及其优劣分析&#xff0c;结合你的 Vue 项目需求&#xff0c;我会在最后给出推荐方案&#xff1a; 一、主流 Electron 打包工具对比 1. Electron …...

jumpserver 网络安全 网络安全 authenticity

1.1 计算机安全的概念 1.1.1 计算机安全最核心的三个关键目标(CIA)&#xff1a; 保密性(Confidentiality)--①数据保密性&#xff08;确保隐私或秘密不向非授权者泄密或使用&#xff09;&#xff1b; ②隐私性&#xff08;确保个人能够控制或确定其自身相关的信息&#xff09…...

Spring Cloud之远程调用OpenFeign参数传递

目录 OpenFeign参数传递 传递单个参数 传递多个参数 传递对象 传递JSON OpenFeign参数传递 传递单个参数 服务提供方product-service RequestMapping("/product") RestController public class ProductController {Autowiredprivate ProductService productSe…...

详解SQL权限与授予与收回对数据操作权限的操作

授予与收回对数据操作权限 一、GRANT 操作用法示例 二、REVOKE 操作用法示例 三、权限1. 数据库级别权限常见权限&#xff1a; 2. 对象级别权限作用对象&#xff1a;常见权限&#xff1a;对表或视图&#xff1a;对序列&#xff08;Sequence&#xff09;&#xff1a;对存储过程和…...

网络安全之文件上传漏洞

一&#xff0c;文件上传漏洞的原因&#xff1a; 文件上传漏洞的存在主要是因为开发者未对用户上传的文件进行充分的安全验证&#xff0c;导致攻击者可以上传恶意文件&#xff08;如 WebShell、恶意脚本等&#xff09;到服务器&#xff0c;进而控制服务器或实施进一步攻击。 常…...

Fast DDS Security--仿问控制

Fast DDS中提供了两种级别的仿问控制&#xff1a; 1 Domain Governance: 定义域级别的安全策略&#xff08;全局规则&#xff09;. 2 DomainParticipant Permissions &#xff1a; 定义参与者的具体权限&#xff08;个体规则&#xff09; 先说一下Domain Governance&#xf…...

【13】单片机编程核心技巧:乘法运算

【13】单片机编程核心技巧&#xff1a;乘法运算 七律 乘法 乘法运算寄存间&#xff0c;溢出玄机隐字边。 连乘自增简写妙&#xff0c;移位替代速如仙。 中间变量扩疆土&#xff0c;长整型存避险关。 单片机中精算术&#xff0c;毫厘不爽展奇观。 摘要 乘法运算是单片机编程…...

为什么大模型网站使用 SSE 而不是 WebSocket?

在大模型网站&#xff08;如 ChatGPT、Claude、Gemini 等&#xff09;中&#xff0c;前端通常使用 EventSource&#xff08;Server-Sent Events, SSE&#xff09; 来与后端对接&#xff0c;而不是 WebSocket。这是因为 SSE 更适合类似流式文本生成的场景。下面我们详细对比 SSE…...

PostgreSQL的备份方式

PostgreSQL 提供多种方式进行备份&#xff0c;适用于不同需求的场景。常用的备份方法如下&#xff1a; 1. 逻辑备份&#xff08;pg_dump 和 pg_dumpall&#xff09; 1.1 使用 pg_dump 备份单个数据库 pg_dump 是 PostgreSQL 内置的逻辑备份工具&#xff0c;可以将数据库导出为…...

iTextSharp-PDF批量导出

HTML转PDF批量导出速度太慢且使用Spire.pdf.dll限制页签10后需要开通会员才能使用-做出优化 环境&#xff1a;U9 - UI插件 需求&#xff1a;选择需要导出的客户查询对应对账数据批量导出PDF并弹出下载框保存到默认位置 using System; using System.Collections.Generic; us…...

基于Matlab设计GUI图像处理交互界面

Image-Processing-GUI 项目说明 本博文提供了完整的代码和使用教程&#xff0c;适合新入门的朋友参考&#xff0c;完整代码资源文件请转至文末的下载链接。 本项目是《Matlab实践》中图像处理软件题目&#xff0c;本项目实现的具体内容如下 基于Matlab设计GUI交互界面图像的…...

osg安装编译第三方,完整详细过程。 libtiff/tif config.vc.hdoes not exist

第三方安装包下载地址 GitHub - bjornblissing/osg-3rdparty-cmake: CMake scripts for building OpenSceneGraph third party libraries. 在计算机中的布局 D:\CPlus\osg\src\osg-3rdparty\osg-3rdparty-cmake三层布局&#xff0c;src 放置源码 执行里面的批处理文件&#…...

红队OPSEC(安全运营)个人总结

OPSEC又称&#xff1a;运营安全&#xff0c;是指在红队的视角下&#xff0c;蓝队对我方的威胁。 OPSEC漏洞的五个流程&#xff1a; 关键信息识别&#xff1a;指红队的关键信息不泄露&#xff0c;包括但不限于红队的攻击意图&#xff0c;能力&#xff0c;人员&#xff0c;活动及…...

RSA算法:开启现代密码学的数学之钥

一、RSA算法简介 RSA&#xff08;Rivest-Shamir-Adleman&#xff09;是当今应用最广泛的非对称加密算法&#xff0c;由三位科学家Ron Rivest、Adi Shamir和Leonard Adleman于1977年提出。它的核心思想是利用数论中的难题&#xff0c;构建一对数学上关联的密钥——公钥用于加密…...

【从0到1构建实时聊天系统:Spring Boot + Vue3 + WebSocket全栈实战】

一、项目架构 技术栈清单&#xff1a; 后端&#xff1a;Spring Boot 3.0 WebSocket STOMP前端&#xff1a;Vue3 Pinia WebSocket Client部署&#xff1a;Nginx Docker Compose 二、核心功能实现 1. WebSocket双向通信 // 后端配置类 Configuration EnableWebSocketMes…...