C++ 20概述
引言
C++20是C++语言的第五个主要版本,继C++98、C++03、C++11和C++17之后推出。C++20带来了自C++11以来最大规模的语言和标准库更新,引入了多项革命性的特性,使C++编程更加现代化、高效和安全。
本文将概述C++20中引入的主要特性,帮助你了解这些新工具如何提升你的编程体验和代码质量。
C++ 20的主要特性
C++20引入了四个主要的语言特性以及多项标准库扩展:
- 概念(Concepts):提供了编译时类型检查的工具
- 协程(Coroutines):支持协作式多任务处理
- 模块(Modules):替代传统的头文件和包含机制
- 范围(Ranges):提供更优雅的容器操作方式
- 标准库扩展:包括格式库、日历和时区支持等
让我们深入了解每个特性。
概念(Concepts)
概念允许你在模板参数上指定约束条件,这大大提高了模板错误信息的可读性,并使代码更加直观。
基本语法
template <typename T>
concept Integral = std::is_integral_v<T>;
// 使用概念约束模板参数
template <Integral T>
T add(T a, T b) {
return a + b;
}
概念解决了C++模板编程中的一个主要问题:难以理解的编译错误。当使用者传入不符合要求的类型时,编译器会给出清晰的错误消息,而不是冗长的模板实例化错误。
示例:使用概念约束函数
#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)
协程是可以暂停和恢复执行的函数,它们使异步编程、生成器和其他高级控制流成为可能,而无需显式的状态管理或回调函数。
协程基础
协程通过以下三个主要操作定义:
- co_await - 暂停协程,等待某个操作完成
- co_yield - 生成一个值并暂停
- co_return - 完成协程并返回最终值
示例:简单的生成器
#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引入的一项重大变革,旨在取代或补充传统的头文件包含系统。它们解决了头文件的多个问题,包括重复包含、编译时间长、宏泄漏等。
基本语法
// 定义模块 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));
}
模块的主要优点
- 更快的编译时间 - 模块只需编译一次,然后可以被多次导入
- 无宏泄漏 - 模块中的宏不会影响导入它的文件
- 更明确的接口 - 只有标记为
export
的内容才能被外部访问 - 不依赖包含顺序 - 模块导入顺序不重要
模块仍然是C++的一个相对较新的特性,编译器和构建系统支持可能不完全。在生产环境中使用前,应确认你的工具链对模块有良好支持。
范围(Ranges)
范围库是对C++的迭代器系统的重大改进,它让你可以直接对整个容器(而不是迭代器对)进行操作,并允许组合复杂的数据转换操作。
基本用法
#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;
}
范围的主要优点
- 组合性 - 可以像Unix管道一样将多个操作链接起来
- 延迟计算 - 视图只在需要时计算结果
- 更简洁的代码 - 减少了样板代码和临时变量
- 更易读 - 数据转换以声明式而非命令式方式表达
常用视图
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的格式化系统:
#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引入了三路比较运算符,可以简化比较操作的实现:
#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允许以任意顺序初始化结构和类的成员:
struct Point {
int x;
int y;
double z;
};
int main() {
// 可以按任意顺序初始化,并且可以省略某些字段
Point p{.y = 2, .x = 1, .z = 3.0};
return 0;
}
constexpr的改进
C++20大大扩展了可以在编译时计算的内容:
#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:使用范围简化数据处理
假设你正在开发一个应用程序,需要从用户输入的一系列数字中找出所有质数,然后按降序输出它们的平方:
#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:使用协程实现异步操作
下面是一个使用协程实现简单异步任务的示例:
#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支持文档
练习
- 使用概念创建一个只接受算术类型的通用函数模板。
- 使用范围视图实现一个函数,找出一组数字中的所有偶数,并将其按降序排列。
- 尝试使用模块组织一个小型项目。
- 使用格式库创建一个生成格式化报告的函数。
- 设计一个使用协程的简单异步任务系统。
通过这些练习,你将能够实践和巩固对C++20特性的理解,为将它们应用到实际项目中做好准备。