第41章 C++20/23/26新特性 41.1 C++20新特性 41.1.1 概念(Concepts) 概念是C++20中引入的一种约束模板参数的机制,它允许我们在编译时检查模板参数是否满足特定的要求。
41.1.1.1 基本语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <concepts> #include <iostream> template <typename T> concept Arithmetic = std::is_arithmetic_v<T>; template <Arithmetic T> T add (T a, T b) { return a + b; } template <typename T> concept Printable = requires (T t) { { std::cout << t } -> std::same_as<std::ostream&>; }; template <Printable T> void print (T t) { std::cout << t << std::endl; } int main () { std::cout << add (1 , 2 ) << std::endl; std::cout << add (1.5 , 2.5 ) << std::endl; print (42 ); print (3.14 ); print ("Hello" ); return 0 ; }
41.1.1.2 标准概念库 C++20提供了丰富的标准概念,定义在<concepts>头文件中:
std::integral:整型类型std::floating_point:浮点型类型std::same_as:类型相同std::derived_from:派生自某个类型std::convertible_to:可转换为某个类型std::invocable:可调用对象std::range:范围类型41.1.2 模块(Modules) 模块是C++20中引入的一种新的代码组织方式,它替代了传统的头文件包含机制,提供了更好的编译性能和命名空间管理。
41.1.2.1 模块的基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 export module math; export int add (int a, int b) { return a + b; } export double multiply (double a, double b) { return a * b; } import math; #include <iostream> int main () { std::cout << add (1 , 2 ) << std::endl; std::cout << multiply (2.5 , 3.5 ) << std::endl; return 0 ; }
41.1.2.2 模块的优势 编译性能 :模块只需要编译一次,后续使用时直接引用编译结果命名空间隔离 :模块中的名称不会污染全局命名空间依赖管理 :明确的依赖关系,避免了头文件的循环包含接口清晰 :通过export关键字明确导出的接口41.1.3 协程(Coroutines) 协程是C++20中引入的一种轻量级线程,它允许函数在执行过程中暂停并在稍后恢复执行。
41.1.3.1 基本语法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <coroutine> #include <iostream> #include <future> template <typename T>struct task { struct promise_type { T value; std::coroutine_handle<promise_type> handle; task get_return_object () { return task{std::coroutine_handle<promise_type>::from_promise (*this )}; } std::suspend_never initial_suspend () noexcept { return {}; } std::suspend_never final_suspend () noexcept { return {}; } void return_value (T v) { value = v; } void unhandled_exception () { std::terminate (); } }; std::coroutine_handle<promise_type> handle; T get () { return handle.promise ().value; } ~task () { if (handle) handle.destroy (); } }; task<int > async_add (int a, int b) { std::cout << "Async add started" << std::endl; co_await std::suspend_always{}; std::cout << "Async add resumed" << std::endl; co_return a + b; } int main () { auto t = async_add (1 , 2 ); std::cout << "Main thread continues" << std::endl; t.handle.resume (); std::cout << "Result: " << t.get () << std::endl; return 0 ; }
41.1.3.2 协程的应用场景 异步I/O :避免线程阻塞生成器 :按需生成序列状态机 :简化状态管理协作式多任务 :轻量级线程41.1.4 范围库(Ranges) 范围库是C++20中引入的一种新的迭代器抽象,它提供了更简洁、更灵活的方式来处理序列。
41.1.4.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 #include <ranges> #include <vector> #include <iostream> #include <algorithm> int main () { std::vector<int > numbers = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; auto even_numbers = numbers | std::views::filter ([](int n) { return n % 2 == 0 ; }); std::ranges::for_each(even_numbers, [](int n) { std::cout << n << " " ; }); std::cout << std::endl; auto result = numbers | std::views::filter ([](int n) { return n > 3 ; }) | std::views::transform ([](int n) { return n * 2 ; }) | std::views::take (3 ); std::ranges::for_each(result, [](int n) { std::cout << n << " " ; }); std::cout << std::endl; return 0 ; }
41.1.4.2 常用范围适配器 std::views::filter:过滤元素std::views::transform:转换元素std::views::take:取前N个元素std::views::drop:跳过前N个元素std::views::reverse:反转序列std::views::join:连接序列std::views::zip:合并多个序列41.1.5 三路比较运算符(<=>) 三路比较运算符是C++20中引入的一种新运算符,它可以同时比较两个值的大小关系。
41.1.5.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <compare> #include <iostream> struct Point { int x, y; auto operator <=>(const Point&) const = default ; }; int main () { Point a{1 , 2 }; Point b{1 , 3 }; Point c{2 , 1 }; std::cout << "a < b: " << (a < b) << std::endl; std::cout << "a == b: " << (a == b) << std::endl; std::cout << "a > c: " << (a > c) << std::endl; auto result = a <=> b; if (result < 0 ) { std::cout << "a < b" << std::endl; } else if (result == 0 ) { std::cout << "a == b" << std::endl; } else { std::cout << "a > b" << std::endl; } return 0 ; }
41.1.5.2 自定义三路比较 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <compare> #include <string> #include <iostream> struct Person { std::string name; int age; auto operator <=>(const Person& other) const { if (auto cmp = name <=> other.name; cmp != 0 ) { return cmp; } return age <=> other.age; } bool operator ==(const Person& other) const = default ; }; int main () { Person p1{"Alice" , 30 }; Person p2{"Bob" , 25 }; Person p3{"Alice" , 28 }; std::cout << "p1 < p2: " << (p1 < p2) << std::endl; std::cout << "p1 > p3: " << (p1 > p3) << std::endl; std::cout << "p1 == p3: " << (p1 == p3) << std::endl; return 0 ; }
41.1.6 consteval和constinit C++20引入了两个新的关键字:consteval和constinit,用于控制常量表达式的计算时机。
41.1.6.1 consteval consteval关键字用于声明一个立即函数,该函数必须在编译时求值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <iostream> consteval int factorial (int n) { return n <= 1 ? 1 : n * factorial (n - 1 ); } int main () { constexpr int f5 = factorial (5 ); std::cout << "5! = " << f5 << std::endl; return 0 ; }
41.1.6.2 constinit constinit关键字用于声明一个变量,该变量必须在编译时初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> constinit int global_var = 42 ;constexpr int compute () { return 100 ; } int main () { constinit int local_var = compute (); std::cout << "global_var: " << global_var << std::endl; std::cout << "local_var: " << local_var << std::endl; global_var = 100 ; local_var = 200 ; std::cout << "global_var after modification: " << global_var << std::endl; std::cout << "local_var after modification: " << local_var << std::endl; return 0 ; }
41.1.7 std::span std::span是C++20中引入的一种非拥有式的容器视图,它提供了对连续内存的安全访问。
41.1.7.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <span> #include <vector> #include <array> #include <iostream> void print_elements (std::span<int > elements) { for (int element : elements) { std::cout << element << " " ; } std::cout << std::endl; } int main () { std::vector<int > vec = {1 , 2 , 3 , 4 , 5 }; std::span<int > vec_span (vec) ; print_elements (vec_span); std::array<int , 3> arr = {6 , 7 , 8 }; std::span<int > arr_span (arr) ; print_elements (arr_span); int raw_arr[] = {9 , 10 , 11 }; std::span<int > raw_span (raw_arr) ; print_elements (raw_span); std::span<int > sub_span = vec_span.subspan (1 , 3 ); print_elements (sub_span); return 0 ; }
41.1.7.2 span的优势 非拥有式 :不管理内存,只是视图类型安全 :提供类型安全的访问边界检查 :在调试模式下提供边界检查灵活 :可以从各种容器创建std::format是C++20中引入的一种新的格式化输出函数,它提供了类似Python的格式化语法。
41.1.8.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <format> #include <string> #include <iostream> int main () { std::string s1 = std::format("Hello, {}" , "world" ); std::cout << s1 << std::endl; std::string s2 = std::format("{}, {} and {}" , "Alice" , "Bob" , "Charlie" ); std::cout << s2 << std::endl; std::string s3 = std::format("{name} is {age} years old" , std::make_format_args ("name" _a="Alice" , "age" _a=30 )); std::cout << s3 << std::endl; std::string s4 = std::format("Pi is approximately {:.2f}" , 3.14159 ); std::cout << s4 << std::endl; auto now = std::chrono::system_clock::now (); std::string s5 = std::format("Current time: {:%Y-%m-%d %H:%M:%S}" , now); std::cout << s5 << std::endl; return 0 ; }
41.1.8.2 格式化说明符 宽度 :{:10} 表示宽度为10对齐 :{:<10} 左对齐,{:^10} 居中,{:>10} 右对齐填充 :{:*>10} 用*填充精度 :{:.2f} 小数点后2位进制 :{:x} 十六进制,{:o} 八进制,{:b} 二进制41.1.9 std::jthread std::jthread是C++20中引入的一种新的线程类,它是std::thread的改进版本,会自动管理线程的生命周期。
41.1.9.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <thread> #include <iostream> #include <chrono> int main () { std::jthread t ([]() { for (int i = 0 ; i < 5 ; ++i) { std::cout << "Thread working... " << i << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1 )); } }) ; std::cout << "Main thread continues" << std::endl; std::this_thread::sleep_for (std::chrono::seconds (3 )); t.request_stop (); std::cout << "Main thread exiting" << std::endl; return 0 ; }
41.1.9.2 jthread的优势 自动join :在析构时自动join,避免线程泄漏可停止 :支持请求线程停止RAII :符合RAII原则41.1.10 其他C++20特性 初始化语句在if/switch中 :
1 2 3 4 5 if (auto [ptr, ec] = open_file ("test.txt" ); ec) { std::cerr << "Error opening file: " << ec.message () << std::endl; } else { }
指定初始化器 :
1 2 3 4 5 struct Point { int x, y; }; Point p = {.x = 1 , .y = 2 };
constexpr lambda :
1 2 3 4 5 constexpr auto add = [](int a, int b) { return a + b; }; constexpr int result = add (1 , 2 );
lambda中的this捕获 :
1 2 3 4 5 6 7 8 9 10 struct MyClass { int value = 42 ; auto getLambda () { return [*this ]() { return value; }; } };
41.2 C++23新特性 41.2.1 std::expected std::expected是C++23中引入的一种新类型,它可以表示成功的值或错误信息。
41.2.1.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <expected> #include <string> #include <iostream> std::expected<int , std::string> divide (int a, int b) { if (b == 0 ) { return std::unexpected ("Division by zero" ); } return a / b; } int main () { auto result1 = divide (10 , 2 ); if (result1) { std::cout << "Result: " << *result1 << std::endl; } else { std::cout << "Error: " << result1. error () << std::endl; } auto result2 = divide (10 , 0 ); if (result2) { std::cout << "Result: " << *result2 << std::endl; } else { std::cout << "Error: " << result2. error () << std::endl; } return 0 ; }
41.2.1.2 链式操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include <expected> #include <string> #include <iostream> std::expected<int , std::string> parse_int (const std::string& s) { try { return std::stoi (s); } catch (...) { return std::unexpected ("Invalid integer" ); } } std::expected<int , std::string> add_one (int n) { if (n < 0 ) { return std::unexpected ("Negative number" ); } return n + 1 ; } int main () { std::string input = "42" ; auto result = parse_int (input) .and_then (add_one) .and_then ([](int n) { return add_one (n); }); if (result) { std::cout << "Result: " << *result << std::endl; } else { std::cout << "Error: " << result.error () << std::endl; } return 0 ; }
41.2.2 std::mdspan std::mdspan是C++23中引入的一种多维数组视图,它提供了对连续内存的多维访问。
41.2.2.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <mdspan> #include <vector> #include <iostream> int main () { std::vector<int > data = {1 , 2 , 3 , 4 , 5 , 6 }; std::mdspan<int , std::extents<size_t , 2 , 3 >> matrix (data.data ()); matrix (0 , 0 ) = 10 ; matrix (1 , 2 ) = 20 ; for (size_t i = 0 ; i < matrix.extent (0 ); ++i) { for (size_t j = 0 ; j < matrix.extent (1 ); ++j) { std::cout << matrix (i, j) << " " ; } std::cout << std::endl; } return 0 ; }
41.2.2.2 动态维度 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <mdspan> #include <vector> #include <iostream> int main () { std::vector<int > data = {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }; std::mdspan<int , std::extents<size_t , 2 , 2 , 2 >> tensor (data.data ()); tensor (0 , 1 , 0 ) = 100 ; for (size_t i = 0 ; i < tensor.extent (0 ); ++i) { for (size_t j = 0 ; j < tensor.extent (1 ); ++j) { for (size_t k = 0 ; k < tensor.extent (2 ); ++k) { std::cout << tensor (i, j, k) << " " ; } std::cout << std::endl; } std::cout << std::endl; } return 0 ; }
41.2.3 std::print std::print是C++23中引入的一种新的输出函数,它直接将格式化的内容输出到标准输出。
41.2.3.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <print> int main () { std::print ("Hello, {}\n" , "world" ); std::print ("{}, {} and {}\n" , "Alice" , "Bob" , "Charlie" ); std::print ("Pi is approximately {:.2f}\n" , 3.14159 ); std::print ("{:<10} {:>10}\n" , "Left" , "Right" ); return 0 ; }
41.2.3.2 std::println std::println是std::print的变体,它会自动添加换行符。
1 2 3 4 5 6 7 #include <print> int main () { std::println ("Hello, {}" , "world" ); std::println ("The answer is {}" , 42 ); return 0 ; }
41.2.4 其他C++23特性 41.3 C++26新特性 41.3.1 静态反射 静态反射是C++26中引入的一种新特性,它允许在编译时获取类型的信息。
41.3.1.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <reflect> #include <iostream> struct Point { int x, y; }; int main () { constexpr auto point_type =反射::type_of <Point>(); for_each(point_type.members (), [](auto member) { std::cout << "Member: " << member.name () << std::endl; std::cout << "Type: " << member.type ().name () << std::endl; }); Point p{1 , 2 }; auto x_member = point_type.member ("x" ); x_member.set (p, 10 ); std::cout << "p.x = " << x_member.get (p) << std::endl; return 0 ; }
41.3.1.2 反射的应用 序列化/反序列化 :自动生成序列化代码对象关系映射 :自动生成数据库映射代码依赖注入 :自动生成依赖注入代码属性系统 :实现类似C#的属性系统41.3.2 模式匹配 模式匹配是C++26中引入的一种新特性,它允许在switch语句和if语句中使用更灵活的模式。
41.3.2.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <variant> #include <string> #include <iostream> using Value = std::variant<int , double , std::string>;void process (Value v) { switch (v) { case int i: std::cout << "Integer: " << i << std::endl; break ; case double d: std::cout << "Double: " << d << std::endl; break ; case std::string s: std::cout << "String: " << s << std::endl; break ; } } int main () { process (42 ); process (3.14 ); process ("Hello" ); return 0 ; }
41.3.2.2 结构化绑定模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct Point { int x, y; }; void process (Point p) { switch (p) { case Point{0 , 0 }: std::cout << "Origin" << std::endl; break ; case Point{x: 0 }: std::cout << "On y-axis" << std::endl; break ; case Point{y: 0 }: std::cout << "On x-axis" << std::endl; break ; default : std::cout << "Other point" << std::endl; break ; } }
41.3.3 网络库 网络库是C++26中引入的一种新的标准库,它提供了跨平台的网络编程功能。
41.3.3.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <network> #include <iostream> int main () { network::tcp::server server (8080 ) ; server.on_accept ([](auto client) { std::cout << "Client connected" << std::endl; client.on_read ([](auto data) { std::cout << "Received: " << std::string (data.begin (), data.end ()) << std::endl; }); client.on_disconnect ([] { std::cout << "Client disconnected" << std::endl; }); }); std::cout << "Server listening on port 8080" << std::endl; server.run (); return 0 ; }
41.3.3.2 HTTP客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <network> #include <iostream> int main () { network::http::client client; auto response = client.get ("https://example.com" ); std::cout << "Status: " << response.status () << std::endl; std::cout << "Body: " << response.body () << std::endl; return 0 ; }
41.3.4 执行策略 执行策略是C++26中引入的一种新特性,它允许在算法中指定执行方式。
41.3.4.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <algorithm> #include <execution> #include <vector> #include <iostream> int main () { std::vector<int > v = {5 , 2 , 8 , 1 , 9 , 3 }; std::sort (std::execution::seq, v.begin (), v.end ()); std::sort (std::execution::par, v.begin (), v.end ()); std::sort (std::execution::par_unseq, v.begin (), v.end ()); for (int n : v) { std::cout << n << " " ; } std::cout << std::endl; return 0 ; }
41.3.5 数学库增强 C++26中对数学库进行了增强,添加了许多新的数学函数。
41.3.5.1 基本用法 1 2 3 4 5 6 7 8 9 10 11 12 13 #include <cmath> #include <iostream> int main () { double x = 0.5 ; std::cout << "erfcinv(" << x << ") = " << std::erfcinv (x) << std::endl; std::cout << "expint(" << x << ") = " << std::expint (x) << std::endl; std::cout << "riemann_zeta(" << x << ") = " << std::riemann_zeta (x) << std::endl; return 0 ; }
41.3.6 其他C++26特性 协程的改进 :
模块的改进 :
类型推断的改进 :
安全性增强 :
41.4 现代C++的最佳实践 41.4.1 代码风格 使用概念 :优先使用概念而不是SFINAE使用模块 :优先使用模块而不是头文件使用范围库 :优先使用范围库而不是原始迭代器使用std::format :优先使用std::format而不是printf或std::cout的复杂格式化使用std::expected :优先使用std::expected处理可能失败的操作41.4.2 性能优化 使用std::span :避免不必要的复制使用consteval :将计算移到编译时使用constinit :避免运行时初始化使用协程 :避免线程阻塞使用执行策略 :利用并行计算41.4.3 安全性 使用std::jthread :避免线程泄漏使用std::optional和std::expected :避免空指针和错误处理使用边界检查 :避免越界访问使用RAII :确保资源正确管理41.5 总结 C++20、C++23和C++26标准引入了许多新特性,这些特性使得C++更现代化、更安全、更高效:
C++20 :概念、模块、协程、范围库、三路比较运算符、consteval、constinit、span、format、jthreadC++23 :std::expected、std::mdspan、std::print、deducing this、monadic操作C++26 :静态反射、模式匹配、网络库、执行策略、数学库增强这些新特性不仅提高了代码的可读性和可维护性,也提供了更好的性能和安全性。作为现代C++程序员,我们应该积极学习和使用这些新特性,以编写更优质的C++代码。
同时,我们也应该注意保持代码的兼容性,对于需要支持旧编译器的项目,应该谨慎使用这些新特性,或者使用条件编译来处理不同的编译器版本。
通过不断学习和实践现代C++的新特性,我们可以充分发挥C++的优势,编写更加高效、安全、可维护的代码。