C++20 是 C++ 语言发展中的一个重要里程碑,引入了众多新特性,这些特性不仅提升了语言的功能性和性能,还极大地简化了许多编程任务。以下将详细介绍 C++20 的一些关键新特性,包括语法和实际使用示例。
1.Ranges 库
1.1. 概述
Ranges 库是对标准模板库(STL)的一个重要扩展。它重新定义了容器和算法的交互方式,使代码更具可读性和表达力。Ranges 库的设计目标是减少代码中复杂的迭代器操作,使程序员能够以声明式风格操作序列数据。
1.2. 核心组件
views:提供视图(views)以惰性评估(lazy evaluation)的方式对序列进行操作。
actions:用于直接修改序列的内容,类似于视图,但是立即执行的。
adaptors:可以将多个视图组合在一起,例如 filter 和 transform。
1.3. 使用示例
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// 使用 ranges 进行惰性计算:过滤和变换
auto even_squares = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
// 遍历输出结果
for (int n : even_squares) {
std::cout << n << " "; // 输出:4 16 36 64
}
}
分析:
views::filter:过滤序列中的元素,仅保留符合条件的元素。
views::transform:将保留的元素进行转换操作,在这里进行了平方运算。
惰性计算:此操作不会立即对所有元素进行过滤和转换,只有在访问元素时才会计算结果,节省了不必要的开销。
- 三向比较运算符(<=>)
2.1. 概述
三向比较运算符,俗称“太空飞船运算符”(<=>),是 C++20 引入的一种新型比较运算符。它简化了多重比较运算的实现过程,自动生成 ==、!=、<、<=、>、>= 等运算符。
2.2. 使用示例
#include <iostream>
#include <compare>
struct Point {
int x, y;
// 自动生成所有比较运算符
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1 = {1, 2};
Point p2 = {1, 3};
if (p1 < p2) {
std::cout << "p1 is less than p2\n";
}
if (p1 != p2) {
std::cout << "p1 is not equal to p2\n";
}
}
输出:
p1 is less than p2
p1 is not equal to p2
分析:
<=> 运算符:在结构体 Point 中引入此运算符后,编译器会自动生成所有比较运算符的实现,减少了代码冗余。
默认实现:通过 = default 关键字,编译器将生成默认的三向比较逻辑。
- Coroutines(协程)
3.1. 概述
协程是一种特殊的函数,允许在执行过程中暂停并在稍后恢复。C++20 原生支持协程,使异步编程和生成器的实现更加简洁和高效。与传统的多线程不同,协程通过协作式多任务处理减少了上下文切换的开销。
3.2. 核心组件
co_await:用于等待异步操作的完成。
co_yield:用于生成一个值并暂停协程。
co_return:用于返回结果并结束协程。
3.3. 使用示例
#include <iostream>
#include <coroutine>
struct Generator {
struct promise_type {
int current_value;
auto get_return_object() {
return Generator{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto yield_value(int value) {
current_value = value;
return std::suspend_always{};
}
void return_void() {}
void unhandled_exception() { std::exit(1); }
};
std::coroutine_handle<promise_type> handle;
Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { handle.destroy(); }
bool next() {
handle.resume();
return not handle.done();
}
int value() const { return handle.promise().current_value; }
};
Generator counter(int n) {
for (int i = 1; i <= n; ++i) {
co_yield i;
}
}
int main() {
auto gen = counter(5);
while (gen.next()) {
std::cout << gen.value() << " ";
}
}
//输出 1 2 3 4 5
分析:
co_yield:在每次循环中,co_yield 会暂停协程并返回当前值,直到协程被再次恢复。
promise_type:定义了协程的行为,包括如何暂停和恢复,以及如何处理返回值和异常。
std::coroutine_handle:用于控制协程的执行,如恢复、销毁等。
- Concepts(概念)
4.1. 概述
Concepts 是 C++20 引入的一种用于约束模板参数的新特性。Concepts 通过编译时检查,确保模板参数满足某些条件,从而使模板代码更加安全和易于调试。它是 C++ 模板编程的重要增强,使得模板的使用更加简洁和清晰。
4.2. 核心组件
概念定义:通过 concept 关键字定义概念,可以用来约束模板参数。
约束模板参数:使用概念来限制模板参数的类型或行为。
4.3. 使用示例
#include <iostream>
#include <concepts>
// 定义一个概念
template<typename T>
concept Integral = std::is_integral_v<T>;
template<Integral T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << std::endl; // 正常编译
// std::cout << add(1.0, 2.0) << std::endl; // 编译错误
}
- std::span
5.1. 概述
std::span 是一个轻量级的视图类型,表示一段连续内存的子集。它类似于指针和数组,但更安全、更易用。std::span 提供了与数组或容器类似的接口,但不负责管理内存,因此可以用于多个不同的数据源。
5.2. 使用示例
#include <iostream>
#include <span>
#include <vector>
void print_span(std::span<int> s) {
for (int n : s) {
std::cout << n << " ";
}
std::cout << std::endl;
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
std::vector<int> vec = {6, 7, 8, 9, 10};
print_span(arr); // 使用数组初始化 std::span
print_span(vec); // 使用 vector 初始化 std::span
}
//输出
//1 2 3 4 5
//6 7 8 9 10
分析:
安全性:std::span 提供了类似指针的接口,但不会产生指针的安全问题(如越界访问)。
灵活性:std::span 可以用于数组、std::vector 等不同的数据结构,而无需拷贝数据。
- constinit 关键字
6.1. 概述
constinit 关键字用于保证变量在程序启动时是静态初始化的。与 constexpr 和 const 关键字不同,constinit 用于确保变量在编译期是静态初始化的,避免在多线程环境中可能发生的重复初始化问题。
6.2. 使用示例
#include <iostream>
constinit int x = 42;
int main() {
std::cout << x << std::endl; // 输出: 42
}
分析:
静态初始化保证:constinit 确保变量在程序启动时被静态初始化,从而避免了在多线程环境中可能出现的重复初始化问题。
编译期检查:如果 constinit 变量没有被静态初始化,编译器会给出错误提示。
- Lambda 表达式的增强
7.1. 概述
C++20 对 Lambda 表达式进行了若干增强,使其更加灵活和强大。主要包括捕获列表中的 this 和模板 Lambda。
模板 Lambda:
#include <iostream>
#include <vector>
int main() {
auto add = [](auto a, auto b) { return a + b; };
std::cout << add(1, 2) << std::endl; // 输出: 3
std::cout << add(1.5, 2.3) << std::endl; // 输出: 3.8
}
捕获 this:
#include <iostream>
struct S {
int value = 42;
void print() {
auto lambda = [*this] { std::cout << value << std::endl; };
lambda();
}
};
int main() {
S s;
s.print(); // 输出: 42
}
分析:
模板 Lambda:允许 Lambda 表达式接受不同类型的参数,极大地增强了 Lambda 的通用性。
捕获 this:使用 [*this] 捕获当前对象的拷贝,而不是引用,避免了 Lambda 中出现悬空引用的问题。
- std::jthread
8.1. 概述
std::jthread 是 C++20 引入的新线程类,它与 std::thread 类似,但自动管理线程的生命周期。std::jthread 提供了一种更安全的方式来处理线程,当 std::jthread 对象销毁时,它会自动加入(join)线程。
8.2. 使用示例
#include <iostream>
#include <thread>
#include <chrono>
void task() {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task finished\n";
}
int main() {
std::jthread t(task); // std::jthread 会自动管理线程的生命周期
std::cout << "Main thread\n";
} // t 作用域结束时自动 join
输出:
Main thread
Task finished
分析:
生命周期管理:std::jthread 在作用域结束时自动调用 join,避免了可能导致程序崩溃的未 join 线程。
可取消性:std::jthread 还支持通过传递 stop_token 来取消正在执行的线程操作。
总结
C++20 是 C++ 标准的一次重大升级,它引入了诸如 Ranges 库、协程、概念、模块和三向比较运算符等众多新特性。这些特性极大地增强了语言的表达能力,使代码更简洁、更高效、更易于维护。通过合理利用这些新特性,开发者可以编写出更具现代感的 C++ 代码,同时提高程序的性能和安全性。