跳到主要内容

C++ 20概述

引言

C++20是C++语言的第五个主要版本,继C++98、C++03、C++11和C++17之后推出。C++20带来了自C++11以来最大规模的语言和标准库更新,引入了多项革命性的特性,使C++编程更加现代化、高效和安全。

本文将概述C++20中引入的主要特性,帮助你了解这些新工具如何提升你的编程体验和代码质量。

C++ 20的主要特性

C++20引入了四个主要的语言特性以及多项标准库扩展:

  1. 概念(Concepts):提供了编译时类型检查的工具
  2. 协程(Coroutines):支持协作式多任务处理
  3. 模块(Modules):替代传统的头文件和包含机制
  4. 范围(Ranges):提供更优雅的容器操作方式
  5. 标准库扩展:包括格式库、日历和时区支持等

让我们深入了解每个特性。

概念(Concepts)

概念允许你在模板参数上指定约束条件,这大大提高了模板错误信息的可读性,并使代码更加直观。

基本语法

cpp
template <typename T>
concept Integral = std::is_integral_v<T>;

// 使用概念约束模板参数
template <Integral T>
T add(T a, T b) {
return a + b;
}

概念解决了C++模板编程中的一个主要问题:难以理解的编译错误。当使用者传入不符合要求的类型时,编译器会给出清晰的错误消息,而不是冗长的模板实例化错误。

示例:使用概念约束函数

cpp
#include <concepts>
#include <iostream>

// 定义一个概念:可排序的
template <typename T>
concept Sortable = requires(T& a, T& b) {
{ a < b } -> std::convertible_to<bool>;
std::swap(a, b);
};

// 使用概念约束函数模板
template <Sortable T>
void sort_elements(T* arr, size_t size) {
for (size_t i = 0; i < size; ++i) {
for (size_t j = i + 1; j < size; ++j) {
if (arr[j] < arr[i]) {
std::swap(arr[i], arr[j]);
}
}
}
}

int main() {
int numbers[] = {5, 3, 8, 1, 2};
sort_elements(numbers, 5);

for (int n : numbers) {
std::cout << n << " ";
}
// 输出: 1 2 3 5 8

return 0;
}
提示

概念不仅仅是文档化约束,它们实际上会在编译期间强制执行这些约束。这有助于及早发现错误并提供更有意义的错误消息。

协程(Coroutines)

协程是可以暂停和恢复执行的函数,它们使异步编程、生成器和其他高级控制流成为可能,而无需显式的状态管理或回调函数。

协程基础

协程通过以下三个主要操作定义:

  1. co_await - 暂停协程,等待某个操作完成
  2. co_yield - 生成一个值并暂停
  3. co_return - 完成协程并返回最终值

示例:简单的生成器

cpp
#include <coroutine>
#include <iostream>
#include <exception>

template <typename T>
struct Generator {
struct promise_type {
T value;

Generator 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{}; }
void unhandled_exception() { std::terminate(); }

auto yield_value(T v) {
value = v;
return std::suspend_always{};
}
void return_void() {}
};

std::coroutine_handle<promise_type> handle;

Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }

T next() {
handle.resume();
return handle.promise().value;
}

bool done() const { return handle.done(); }
};

// 一个简单的生成斐波那契数列的协程
Generator<int> fibonacci(int n) {
int a = 0, b = 1;
for (int i = 0; i < n; ++i) {
co_yield a;
int tmp = a;
a = b;
b = tmp + b;
}
}

int main() {
auto fib = fibonacci(10);
while (!fib.done()) {
std::cout << fib.next() << " ";
}
// 输出: 0 1 1 2 3 5 8 13 21 34

return 0;
}
警告

C++20协程是低级别的功能,需要相当多的样板代码来使用。大多数开发者会使用基于协程构建的高级库,而不是直接操作这些原语。

模块(Modules)

模块是C++20引入的一项重大变革,旨在取代或补充传统的头文件包含系统。它们解决了头文件的多个问题,包括重复包含、编译时间长、宏泄漏等。

基本语法

cpp
// 定义模块 math.cppm
export module math;

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

export int subtract(int a, int b) {
return a - b;
}

// 使用模块
import math;

int main() {
return add(5, subtract(10, 3));
}

模块的主要优点

  1. 更快的编译时间 - 模块只需编译一次,然后可以被多次导入
  2. 无宏泄漏 - 模块中的宏不会影响导入它的文件
  3. 更明确的接口 - 只有标记为export的内容才能被外部访问
  4. 不依赖包含顺序 - 模块导入顺序不重要
备注

模块仍然是C++的一个相对较新的特性,编译器和构建系统支持可能不完全。在生产环境中使用前,应确认你的工具链对模块有良好支持。

范围(Ranges)

范围库是对C++的迭代器系统的重大改进,它让你可以直接对整个容器(而不是迭代器对)进行操作,并允许组合复杂的数据转换操作。

基本用法

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

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

// 使用视图过滤偶数并将它们翻倍
auto result = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; });

for (int n : result) {
std::cout << n << " ";
}
// 输出: 4 8 12 16 20

return 0;
}

范围的主要优点

  1. 组合性 - 可以像Unix管道一样将多个操作链接起来
  2. 延迟计算 - 视图只在需要时计算结果
  3. 更简洁的代码 - 减少了样板代码和临时变量
  4. 更易读 - 数据转换以声明式而非命令式方式表达

常用视图

C++20提供了多种预定义视图:

  • std::views::filter - 过滤元素
  • std::views::transform - 转换元素
  • std::views::take - 获取前n个元素
  • std::views::drop - 跳过前n个元素
  • std::views::reverse - 反转范围
  • 以及更多...

其他重要的C++20特性

格式库

C++20引入了std::format,这是一种现代的、类型安全的格式化机制,灵感来自Python的格式化系统:

cpp
#include <format>
#include <iostream>

int main() {
std::string name = "Alice";
int age = 30;

// 使用命名参数
std::cout << std::format("Hello, {0}! You are {1} years old.", name, age) << std::endl;

// 可以指定格式
double value = 3.14159;
std::cout << std::format("Pi with 2 decimal places: {:.2f}", value) << std::endl;

return 0;
}

宇宙飞船运算符(<=>)

C++20引入了三路比较运算符,可以简化比较操作的实现:

cpp
#include <iostream>
#include <compare>

class Point {
public:
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" << std::endl;
}

return 0;
}

指定初始化

C++20允许以任意顺序初始化结构和类的成员:

cpp
struct Point {
int x;
int y;
double z;
};

int main() {
// 可以按任意顺序初始化,并且可以省略某些字段
Point p{.y = 2, .x = 1, .z = 3.0};

return 0;
}

constexpr的改进

C++20大大扩展了可以在编译时计算的内容:

cpp
#include <vector>
#include <algorithm>

constexpr bool is_sorted(const std::vector<int>& v) {
return std::is_sorted(v.begin(), v.end());
}

constexpr std::vector<int> get_sorted_vector() {
std::vector<int> v = {5, 3, 1, 4, 2};
std::sort(v.begin(), v.end());
return v;
}

int main() {
constexpr auto v = get_sorted_vector();
static_assert(is_sorted(v));

return 0;
}

实际应用案例

案例1:使用范围简化数据处理

假设你正在开发一个应用程序,需要从用户输入的一系列数字中找出所有质数,然后按降序输出它们的平方:

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

bool is_prime(int n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;

for (int i = 5; i * i <= n; i += 6) {
if (n % i == 0 || n % (i + 2) == 0) {
return false;
}
}
return true;
}

int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

auto result = numbers
| std::views::filter(is_prime)
| std::views::transform([](int n) { return n * n; })
| std::views::reverse;

std::cout << "Squares of prime numbers in descending order: ";
for (int n : result) {
std::cout << n << " ";
}
// 输出: Squares of prime numbers in descending order: 169 121 49 25 9 4

return 0;
}

案例2:使用协程实现异步操作

下面是一个使用协程实现简单异步任务的示例:

cpp
#include <iostream>
#include <coroutine>
#include <thread>
#include <future>

template <typename T>
struct Task {
struct promise_type {
std::promise<T> promise;

Task get_return_object() { return {promise.get_future()}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void unhandled_exception() { promise.set_exception(std::current_exception()); }

void return_value(T value) { promise.set_value(value); }
};

std::future<T> future;
Task(std::future<T> f) : future(std::move(f)) {}

T get() { return future.get(); }
};

Task<int> perform_calculation() {
// 模拟长时间计算
std::cout << "Starting calculation...\n";

// 模拟异步操作
co_await std::suspend_always{};
std::this_thread::sleep_for(std::chrono::seconds(1));

std::cout << "Calculation completed\n";
co_return 42;
}

int main() {
std::cout << "Program start\n";

auto task = perform_calculation();

std::cout << "Doing other work...\n";
std::this_thread::sleep_for(std::chrono::milliseconds(500));

int result = task.get();
std::cout << "Result: " << result << std::endl;

return 0;
}
注意

上述协程示例为了简单起见做了简化。在实际应用中,你可能需要使用更完整的协程库或框架,如C++20协程本身不提供完整的异步运行时。

总结

C++20带来了许多重要的新功能,从根本上改变了我们编写C++代码的方式:

  • 概念(Concepts) 使模板更加易用和可靠
  • 协程(Coroutines) 简化了异步和生成器编程
  • 模块(Modules) 提供了比头文件更好的代码组织方式
  • 范围(Ranges) 让数据转换更加直观和声明式
  • 其他特性 如格式化库、三路比较运算符等进一步提高了语言的易用性

虽然完全掌握C++20需要时间,但开始使用其部分特性可以立即提高代码质量和开发效率。随着编译器支持的改进,C++20将成为未来几年C++开发的基础。

延伸学习资源

如果你想深入学习C++20的各个特性,可以参考以下资源:

  • 《C++20 - The Complete Guide》by Nicolai M. Josuttis
  • CppReference网站的C++20部分
  • C++标准委员会的文档和论文
  • 各大编译器的C++20支持文档

练习

  1. 使用概念创建一个只接受算术类型的通用函数模板。
  2. 使用范围视图实现一个函数,找出一组数字中的所有偶数,并将其按降序排列。
  3. 尝试使用模块组织一个小型项目。
  4. 使用格式库创建一个生成格式化报告的函数。
  5. 设计一个使用协程的简单异步任务系统。

通过这些练习,你将能够实践和巩固对C++20特性的理解,为将它们应用到实际项目中做好准备。