Last updated on 2024-09-28T11:32:59+08:00
语言特性 三路比较运算符 三路比较运算符表达式的形式为:左操作数 <=> 右操作数
,表达式返回一个对象,使得
若左操作数 <
右操作数则(a <=> b) < 0
若左操作数 >
右操作数则(a <=> b) > 0
而若左操作数和右操作数相等 / 等价则(a <=> b) == 0
1 2 3 4 5 6 7 8 9 10 11 12 13 int main () { double foo = -0.0 ; double bar = 0.0 ; auto res = foo <=> bar; if (res < 0 ) std::cout << "-0 小于 0" ; else if (res > 0 ) std::cout << "-0 大于 0" ; else std::cout << "-0 与 0 相等 " ; }
范围 for
中的初始化语句和初始化器 继 C++ 17 中在 if
和switch
语句中添加初始化器后,C++ 20 在范围 for
中也实现了这个功能
1 2 for (auto n = v.size (); auto i : v) std::cout << --n + i << ' ' ;
consteval
consteval
指定函数是立即函数(immediate function),即每次调用该函数必须产生编译时常量。如果不能在编译期间执行,则编译失败
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 consteval int sqr (int n) { return n * n; }constexpr int r = sqr (100 ); int x = 100 ;int r2 = sqr (x); consteval int sqrsqr (int n) { return sqr (sqr (n)); } constexpr int dblsqr (int n) { return 2 * sqr (n); }
constint
constinit
断言变量拥有静态初始化,即零初始化与常量初始化,否则程序非良构
1 2 3 4 5 const char *g () { return "dynamic initialization" ; }constexpr const char *f (bool p) { return p ? "constant initializer" : g (); } constinit const char *c = f (true );
概念(concepts) 概念(concepts)就是一种编译时谓词,指出一个或多个类型应如何使用,其能用于进行模板实参的编译时校验,以及基于类型属性的函数派发。
例如在老版本的 C++,如果想要定义一个只针对某个类型的函数模板,就只能通过类型萃取机制如 enable_if_t
写一些又臭又长的代码。例如想声明一个只针对整数的函数模板
1 2 3 4 5 template <typename T>auto mod (std::enable_if_t <std::is_integral_v<T>, T> d) { return d % 10 ; }
如果约束条件简单还行,但是如果条件复杂,则代码就会又臭又长,且难以进行复用。而在 C++ 20 中引入了 concepts,此时我们就可以用 concepts 来指定函数类型,例如:
1 2 3 4 5 6 7 8 template <class T >concept integral = std::is_integral_v<T>; template <integral T>auto mod (T d) { return d % 10 ; }
约束 约束是逻辑操作和操作数的序列,它了指定对模板实参的要求。它们可以在 requires
表达式中出现,也可以直接作为概念的主体。例如这里使用 requires
约束表达式写一个针对 utf-8 的 string
的约束类型u8string_t
1 2 3 4 5 template <typename T>concept u8string_t = requires (T t) { t += u8"" ; };
接着以这个约束类型声明一个模板函数 print
,此时只能能够满足u8string_t
约束的类型才能够匹配当前模板
1 2 3 4 5 template <u8string_t T>auto print (T t) { cout << t << endl; }
此时以不同类型的 string
来尝试调用,此时只有 u8string
调用成功
1 2 3 4 5 6 7 8 9 10 11 12 int main () { string str; u8string str_u8; u16string str_u16; u32string str_u32; print (str); print (str_u8); print (str_u16); print (str_u32); }
协程 协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复执行需要的数据。这样就可以编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途
如果函数的定义进行了下列操作之一,那么它是协程:
1 2 3 4 5 6 7 task<> tcp_echo_server () { char data[1024 ]; while (true ) { std::size_t n = co_await socket.async_read_some (buffer (data)); co_await async_write (socket, buffer(data, n)) ; } }
co_yield
暂停执行并返回一个值(协程无法return
)
1 2 3 4 generator<int > iota (int n = 0 ) { while (true ) co_yield n++; }
1 2 3 lazy<int > f () { co_return 7 ; }
注意:协程不能使用变长实参,普通的 return
语句,或占位符返回类型(auto
或 Concept
)。constexpr
函数、构造函数、析构函数及 main
函数不能是协程
模块 C++ 20 中正式引入了模块的概念,模块是一个用于在翻译单元间分享声明和定义的语言特性。它们可以在某些地方替代使用头文件。其主要优点如下:
没有头文件
声明实现仍然可分离,但非必要
可以显式指定导出哪些类或函数
不需要头文件重复引入宏(include guards
)
模块之间名称可以相同,并且不会冲突
模块只处理一次,编译更快(头文件每次引入都需要处理,需要通过 pragma once
约束)
预处理宏只在模块内有效
模块的引入与引入顺序无关
创建模块 1 2 3 4 5 6 7 export module helloworld; import <iostream>; export void hello () { std::cout << "Hello world!\n" ; }
导入模块 1 2 3 4 5 6 import helloworld; int main () { hello (); }
库特性 文本格式化库提供 printf
函数族的安全且可扩展的替用品。有意使之补充既存的 C++ I/O 流库并复用其基础设施,例如对用户定义类型重载的流插入运算符
1 2 3 4 5 6 7 std::string message = std::format("The answer is {}." , 42 ); osyncstreamtemplate < class CharT , class Traits = std::char_traits<CharT>, class Allocator = std::allocator<CharT> > class basic_osyncstream: public std::basic_ostream<CharT, Traits>
类模板 std::basic_osyncstream
是std::basic_syncbuf
的便利包装。它提供机制以同步写入同一流的线程(主要用于解决 std::cout
线程不安全问题)
用法如下:
1 2 3 4 5 6 7 { std::osyncstream sync_out (std::cout) ; sync_out << "Hello, " ; sync_out << "World!" ; sync_out << std::endl; sync_out << "and more!\n" ; }
span
span
是对象的连续序列上的无所有权视图。其所描述的对象能指代对象的相接序列,序列的首元素在零位置。span
能拥有静态长度,该情况下序列中的元素数已知并编码于类型中,或拥有动态长度
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 #include <algorithm> #include <cstddef> #include <iostream> #include <span> template <class T , std::size_t N> [[nodiscard]]constexpr auto slide (std::span<T,N> s, std::size_t offset, std::size_t width) { return s.subspan (offset, offset + width <= s.size () ? width : 0U ); } template <class T , std::size_t N, std::size_t M> [[nodiscard]]constexpr bool starts_with (std::span<T,N> data, std::span<T,M> prefix) { return data.size () >= prefix.size () && std::equal (prefix.begin (), prefix.end (), data.begin ()); } template <class T , std::size_t N, std::size_t M> [[nodiscard]]constexpr bool ends_with (std::span<T,N> data, std::span<T,M> suffix) { return data.size () >= suffix.size () && std::equal (data.end () - suffix.size (), data.end (), suffix.end () - suffix.size ()); } template <class T , std::size_t N, std::size_t M> [[nodiscard]]constexpr bool contains (std::span<T,N> span, std::span<T,M> sub) { return std::search (span.begin (), span.end (), sub.begin (), sub.end ()) != span.end (); } void print (const auto & seq) { for (const auto & elem : seq) std::cout << elem << ' ' ; std::cout << '\n' ; } int main () { constexpr int a[] { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }; constexpr int b[] { 8 , 7 , 6 }; for (std::size_t offset{}; ; ++offset) { constexpr std::size_t width{6 }; auto s = slide (std::span{a}, offset, width); if (s.empty ()) break ; print (s); } static_assert (starts_with (std::span{a}, std::span{a,4 }) && starts_with (std::span{a+1 , 4 }, std::span{a+1 ,3 }) && !starts_with (std::span{a}, std::span{b}) && !starts_with (std::span{a,8 }, std::span{a+1 ,3 }) && ends_with (std::span{a}, std::span{a+6 ,3 }) && !ends_with (std::span{a}, std::span{a+6 ,2 }) && contains (std::span{a}, std::span{a+1 ,4 }) && !contains (std::span{a,8 }, std::span{a,9 })); }
endian
endian
主要用于判断当前机器是大端还是小端(之前只能通过整型截断或者 union
判断,较为麻烦)
若所有标量类型均为小端,则 std::endian::native
等于std::endian::little
若所有标量类型均为大端,则 std::endian::native
等于std::endian::big
若所有标量类型拥有等于 1 的 sizeof
,则端序无影响,且std::endian::little
,std::endian::big
及std::endian::native
三个值相同
若平台使用混合端序,则 std::endian::native
既不等于 std::endian::big
亦不等于std::endian::little
1 2 3 4 5 6 7 8 9 10 11 #include <bit> #include <iostream> int main () { if constexpr (std::endian::native == std::endian::big) std::cout << "big-endian\n" ; else if constexpr (std::endian::native == std::endian::little) std::cout << "little-endian\n" ; else std::cout << "mixed-endian\n" ; }
jthread
jthread
即是通过 RAII 机制封装的 thread
,其会在析构时自动调用join
防止线程 crash。同时其也是可中断的,可以搭配这些中断线程执行的相关类使用:
stop_token
:查询线程是否中断
stop_source
:请求线程停止运行
stop_callback
:stop_token
执行时,可以触发的回调函数
semaphore 信号量是一个轻量级的同步原语,可用来实现任何其他同步概念如 mutex
、shared_mutex
、latches
、barriers
等
根据 LeastMaxValue
不同,主要分为两种:
counting_semaphore
(多元信号量):counting_semaphore
允许同一资源有多于一个同时访问,至少允许 LeastMaxValue
个同时的访问者
binary_semaphore
(二元信号量):是 counting_semaphore
的特化的别名,其 LeastMaxValue
为 1 。实现可能将 binary_semaphore
实现得比 counting_semaphore
的默认实现更高效
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 std::binary_semaphore smphSignal (0 ) ; void ThreadProc () { smphSignal.acquire (); std::cout << "[thread] Got the signal" << std::endl; std::this_thread::sleep_for (3 s); std::cout << "[thread] Send the signal\n" ; smphSignal.release (); } int main () { std::jthread thrWorker (ThreadProc) ; std::cout << "[main] Send the signal\n" ; smphSignal.release (); std::this_thread::sleep_for (50 ms); smphSignal.acquire (); std::cout << "[main] Got the signal\n" ; }
latch
latch
是 std::ptrdiff_t
类型的向下计数器,它能用于同步线程。在创建时初始化计数器的值。其主要有以下特点:
线程可能在 latch 上阻塞直至计数器减少到零。没有可能增加或重置计数器,这使得 latch 为单次使用的屏障
同时调用 latch 的成员函数,除了析构函数,不引入数据竞争
不同于 std::barrier
,参与线程能减少std::latch
多于一次
barrier
类模板 barrier
提供允许至多为期待数量的线程阻塞直至期待数量的线程到达该屏障。不同于 latch
,屏障可重用:一旦到达的线程从屏障阶段的同步点除阻,则可重用同一屏障。屏障对象的生存期由屏障阶段的序列组成。每个阶段定义一个阶段同步点。在阶段中到达屏障的线程能通过调用wait
在阶段同步点上阻塞,而且将保持阻塞直至运行阶段完成步骤
屏障阶段由以下步骤组成:
每次调用 arrive
或arrive_and_drop
减少期待计数
期待计数抵达零时,运行阶段完成步骤。完成步骤调用完成函数对象,并除阻所有在阶段同步点上阻塞的线程。完成步骤的结束强先发生于所有从完成步骤所除阻的调用的返回
对于特化 std::barrier<>
(使用默认模板实参),完成步骤作为对arrive
或arrive_and_drop
的导致期待计数抵达零的调用的一部分运行
对于其他特化,完成步骤在该阶段期间到达屏障的线程之一上运行。而若在完成步骤中调用屏障对象的 wait
以外的成员函数,则行为未定义
完成步骤结束时,重置期待计数为构造中指定的值,可能为 arrive_and_drop
调用所调整,并开始下一阶段
同时调用 barrier
的成员函数,除了析构函数,不引入数据竞争
位运算库 bit
库封装了一些常用的位操作。包括:
bit_cast
:将一个类型的对象表示重解释为另一类型的对象表示byteswap
:反转给定整数值中的字节has_single_bit
:检查一个数是否为二的整数次幂bit_ceil
:寻找不小于给定值的最小的二的整数次幂bit_floor
:寻找不大于给定值的最大的二的整数次幂bit_width
:寻找表示给定值所需的最小位数rotl
:计算逐位左旋转的结果rotr
:计算逐位右旋转的结果countl_zero
:从最高位起计量连续的 0 位的数量countl_one
:从最高位起计量连续的 1 位的数量countr_zero
:从最低位起计量连续的 0 位的数量countr_one
:从最低位起计量连续的 1 位的数量popcount
:计量无符号整数中为 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 namespace std { template <class To, class From> constexpr To bit_cast (const From& from) noexcept ; template <class T > constexpr T byteswap (T value) noexcept ; template <class T> constexpr bool has_single_bit (T x) noexcept ; template <class T> constexpr T bit_ceil (T x) ; template <class T> constexpr T bit_floor (T x) noexcept ; template <class T> constexpr T bit_width (T x) noexcept ; template <class T > [[nodiscard]] constexpr T rotl (T x, int s) noexcept ; template <class T > [[nodiscard]] constexpr T rotr (T x, int s) noexcept ; template <class T> constexpr int countl_zero (T x) noexcept ; template <class T> constexpr int countl_one (T x) noexcept ; template <class T> constexpr int countr_zero (T x) noexcept ; template <class T> constexpr int countr_one (T x) noexcept ; template <class T> constexpr int popcount (T x) noexcept ; enum class endian { little = , big = , native = }; }
ranges
ranges
提供处理元素范围的组件,包括各种视图适配器。其最大的作用就是让我们可以像组装函数一样组装算法,使代码更加高效、便利、可读。提供命名空间别名 std::views
,作为std::ranges::views
的缩写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <ranges> #include <iostream> int main () { auto const ints = {0 ,1 ,2 ,3 ,4 ,5 }; auto even = [](int i) { return 0 == i % 2 ; }; auto square = [](int i) { return i * i; }; for (int i : ints | std::views::filter (even) | std::views::transform (square)) { std::cout << i << ' ' ; } std::cout << '\n' ; for (int i : std::views::transform (std::views::filter (ints, even), square)) { std::cout << i << ' ' ; } }