C++20 新增特性jimmysue.me
- 新增关键字(keywords)
- 新增标识符(Identifies)
-
模块(Modules)
- 优点
- 创建模块
- 引用模块
- import 头文件
-
Ranges
- 例子
-
协程(Coroutines)
- 什么是协程
- 例子(VC++)
-
Concepts
- 如何定义
- 使用
- 例子
-
Lambda 表达式的更新
- [=, this] 需要显式捕获this变量
- 模板形式的 Lambda 表达式
- Lambda 表达式打包捕获(Pack Expansion)
-
常量表达式(constexpr) 的更新
- constexpr string & vector
-
原子(Atomic)智能指针
- 例子
-
自动合流(Joining), 可中断(Cancellable) 的线程
- 例子
-
C++20 同步(Synchronization)库
- std::atomic_ref
-
其他更新
- 指定初始化(Designated Initializers)
- 航天飞机操作符 <=>
- 范围 for 循环语句支持初始化
- 非类型模板形参支持字符串
- [[likely]], [[unlikely]]
- 日历(Calendar)和时区(Timezone)功能
- std::span
- 特性测试宏
- consteval 函数
- constinit
- 用 using 引用 enum 类型
- 格式化库(std::format)
- 增加数学常量
- std::source_location
- [[nodiscard(reason)]]
- 位运算
- 一些小更新
- 参考资料
- concept
- requires
- constinit
- consteval
- co_await
- co_return
- co_yield
- char8_t
- import
- module
- 没有头文件
- 声明实现仍然可分离, 但非必要
- 可以显式指定那些导出(类, 函数等)
- 不需要头文件重复引入宏 (include guards)
- 模块之间名称可以相同不会冲突
- 模块只处理一次, 编译更快 (头文件每次引入都需要处理)
- 预处理宏只在模块内有效
- 模块引入顺序无关紧要
// cppcon.cpp export module cppcon; namespace CppCon { auto GetWelcomeHelper() { return "Welcome to CppCon 2019!"; } export auto GetWelcome() { return GetWelcomeHelper();} }引用模块
// main.cpp import cppcon; int main(){ std::cout << CppCon::GetWelcome(); }import 头文件
- import
- 隐式地将 iostream 转换为模块
- 加速构建, 因为 iostream 只会处理一次
- 和预编译头 (PCH) 具有相似的效果
Ranges 是什么 ?
- Range 代表一串元素, 或者一串元素中的一段
- 类似 begin/end 对
好处:
- 简化语法和方便使用
vectordata{11, 22, 33}; sort(begin(data), end(data)); sort(data); // 使用 Ranges
- 防止 begin/end 不配对
- 使变换/过滤等串联操作成为可能
相关功能
- 视图(View): 延迟计算, 不持有, 不改写
- Actions: 即时处理(eagerly evaluated), 改写
- Algorithms: 所有接受 begin/end 对的算法都可用
- Views 和 actions 使用管道符|串联
- 串联视图
vectordata {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; auto result = data | views::remove_if([](int i) { return i % 2 == 1;}) | views::transform([](int i) { return to_string(i);}); // result = {"2", "4", "6", "8", "10" }; // 注意 以上操作被延迟, 当你遍历result的时候才触发
- 串联actions
vectordata{4, 3, 4, 1, 8, 0, 8}; vectorresult = data | actions::sort | actions::unique;
-
- 排序然后去重
- 操作会原地对data进行更改, 然后返回
- 过滤和变换
int total = accumulate ( view::ints(1) | view::transform([](int i) {return i * i;}) | view::take(10), 0);
-
- view::ints(1) 产生一个无限的整型数列
- 平方
- 取前10个元素, 然后累加(accumulate) 所有的计算延迟到accumulate累加遍历的时候发生
- 它是一个函数
-
具备如下关键字之一:
- co_wait: 挂起协程, 等待其它计算完成
- co_return: 从协程返回 (协程 return 禁止使用)
- co_yield: 同 python yield, 弹出一个值, 挂起协程, 下一次调用继续协程的运行
- for co_await 循环体
for co_await (for-range-declaration: expression) statement
用处
-
简化如下问题的实现:
- generator
- 异步I/O
- 延迟计算
- 事件驱动的程序
experimental::generatorGetSequenceGenerator( int startValue, size_t numberOfValues) { for (int i = 0 startValue; i < startValue + numberOfValues; ++i){ time_t t = system_clock::to_time_t(system_clock::now()); cout << std:: ctime(&t); co_yield i; } } int main() { auto gen = GetSequenceGenerator(10, 5); for (const auto& value : gen) { cout << value << "(Press enter for next value)" << endl; cin.ignore(); } }Concepts
- 对模板类和函数的模板形参的约束
- 编译期断言
- 可声明多个
templateconcept Incrementable = requires(T x) {x++; ++x;};使用
templatevoid Foo(T t); templaterequires Incrementablevoid Foo(T t); templatevoid Foo(T t) requires Incrementable; void Foo(Incrementable auto t);例子
- 具备size() 方法, 且返回size_t
templateconcept HasSize = requires (T x){ {x.size()} -> std::convertible_to; };
- 组合concept
templaterequires Incrementable&& Decrementablevoid Foo(T t); // or templateconcept Incr_Decrementable = Incrementable&& Decrementable; templatevoid Foo(T t);Lambda 表达式的更新 [=, this] 需要显式捕获this变量
- C++20 之前 [=] 隐式捕获this
- C++20 开始 需要显式捕获this: [=, this]
可以在lambda表达式中使用模板语法
[]template(T x) {/* ... */}; []template(T* p) {/* ... */}; []template(T (&a)[N]) {/* ... */};
原因1
- C++20之前: 获取 vector 元素类型, 你需要这么写
auto func = [](auto vec){ using T = typename decltype(vec)::value_type; }
- C++20 你可以:
auto func = [](vectorvec){ // ... }
原因2: 方便获取通用lambda形参类型, 访问静态函数
- c++20 以前
auto func = [](auto const& x){ using T = std::decay_t; T copy = x; T::static_function(); using Iterator = typename T::iterator; }
- C++20 开始
auto func = [](const T& x){ T copy = x; T::static_function(); using Iterator = typename T::iterator; }
原因3: 完美转发
- pre C++20:
auto func = [](auto&& ...args) { return foo(std::forward(args)...); }
- since C++20
auto func = [](T&& …args){ return foo(std::forward(args)...); }Lambda 表达式捕获支持打包展开(Pack Expansion)
- Pre C++20
template auto delay_invoke(F f, Args... args){ return [f, args...]{ return std::invoke(f, args...); } }
- Since C++20
template auto delay_invoke(F f, Args... args){ // Pack Expansion: args = std::move(args)... return [f = std::move(f), args = std::move(args)...](){ return std::invoke(f, args...); } }常量表达式(constexpr) 的更新
-
constexpr 虚函数
- constexpr 的虚函数可以重写非 constexpr 的虚函数
- 非 constexpr 虚函数可以重写 constexpr 的虚函数
-
constexpr 函数可以:
- 使用 dynamic_cast() 和 typeid
- 动态内存分配
- 更改union成员的值
-
包含 try/catch
- 但是不允许throw语句
- 在触发常量求值的时候 try/catch 不发生作用
- 需要开启 constexpr std::vector
- std::string 和 std::vector 类型现在可以作为 constexpr
- 未来需要支持 constexpr 反射
-
智能指针(shared_ptr)线程安全吗?
- 是: 引用计数控制单元线程安全, 保证对象只被释放一次
- 否: 对于数据的读写没有线程安全
-
如何将智能指针变成线程安全?
- 使用 mutex 控制智能指针的访问
-
使用全局非成员原子操作函数访问, 诸如: std::atomic_load(), atomic_store(), …
- 缺点: 容易出错, 忘记使用这些操作
-
C++20: atomic, atomic
- 内部原理可能使用了mutex
- 全局非成员原子操作函数标记为不推荐使用(deprecated)
templateclass concurrent_stack { struct Node { T t; shared_ptrnext; }; atomic_shared_ptrhead; // C++11: 去掉 "atomic_" 并且在访问时, 需要用 // 特殊的函数控制线程安全, 例如用std::tomic_load public: class reference { shared_ptrp;}; auto find(T t) const { auto p = head.load(); // C++11: atomic_load(&head) while (p && p->t != t) p = p->next; return reference(move(p)); } auto front() const { return reference(head); } void push_front(T t) { auto p = make_shared(); p->t = t; p->next = head; while (!head.compare_exchange_weak(p->next, p)){ } // C++11: atomic_compare_exchange_weak(&head, &p->next, p); } void pop_front() { auto p = head.load(); while (p && !head.compare_exchange_weak(p, p->next)) { } // C++11: atomic_compare_exchange_weak(&head, &p, p->next); } };
例子来自 Herb Sutter 的 N4162 论文
自动合流(Joining), 可中断(Cancellable) 的线程-
std::jthread
- 头文件
- 支持中断
-
析构函数中自动 Join
- 析构函数调用 stop_source.request_stop() 然后 join()
-
中断线程执行
- 头文件
-
std::stop_token
- 用来查询线程是否中断
- 可以和condition_variable_any配合使用
-
std::stop_source
- 用来请求线程停止运行
- stop_resources 和 stop_tokens 都可以查询到停止请求
-
std::stop_callback
- 如果对应的stop_token 被要求终止, 将会触发回调函数
- 用法: std::stop_callback myCallback(myStopToken, []{ /* … */ });
- 自动合流 Join
void DoWorkPreCpp20() { std::thread job([] { /* ... */ }); try { // ... Do something else ... } catch (...) { job.join(); throw; // rethrow } job.join(); } void DoWork() { std::jthread job([] { /* ... */ }); // ... Do something else ... } // jthread destructor automatically calls join()
- 中断
std::jthread job([](std::stop_token token) { while (!token.stop_requested()) { //... } }); //... job.request_stop(); // auto source = job.get_stop_source() // auto token = job.get_stop_token()C++20 同步(Synchronization)库
-
信号量(Semaphore), 维基百科请走这里
- 头文件
- 轻量级的同步原语
- 可用来实现任何其他同步概念, 如: mutex, latches, barriers, …
-
两种类型:
- 多元信号量(counting semaphore): 建模非负值资源计数
- 二元信号量(binary semaphore): 只有一个插孔, 两种状态, 最适合实现mutex
-
std::atomic 等待和通知接口
- 等待/阻塞在原子对象直到其值发生改变, 通过通知函数发送通知
- 比轮训(polling)来的更高效
-
方法
- wait()
- notify_one()
- notify_all()
-
锁存器(Latch)和屏障(Barrier)
- 辅助线程条件同步
-
锁存器(Latches)
- 头文件
-
线程的同步点
- 线程将阻塞在这个位置, 直到到达的线程个数达标才放行, 放行之后不再关闭
- 锁存器只会作用一次
-
屏障(Barriers)
- 多个阶段
-
每个阶段中
- 一个参与者运行至屏障点时被阻塞,需要等待其他参与者都到达屏障点, 当到达线程数达标之后
- 阶段完成的回调将被执行
- 线程计数器被重置
- 开启下一阶段
- 线程得以继续执行
- 头文件
- Atomic 引用
- 通过引用访问变为原子操作, 被引用对象可以为非原子类型
struct Data { int anInt = 0; std::string aString; }; Data d{ .aString = "Hello" };航天飞机操作符 <=>
- 正规名称: 三路比较运算符
-
三路比较结果如下
- (a <=> b) < 0 // 如果 a < b 则为 true
- (a <=> b) > 0 // 如果 a > b 则为 true
- (a <=> b) == 0 // 如果 a 与 b 相等或者等价 则为 true
- 类似于C的strcmp 函数返回-1, 0, 1
-
一般情况: 自动生成所有的比较操作符, 如果对象是结构体则逐个比较, 可以用下面代码代替所有的比较运算符
- auto X::operator<=>(const Y&) = default;
- 高级情况: 指定返回类型(支持6种所有的比较运算符)
示例:
class Point { int x; int y; public: friend bool operator==(const Point& a, const Point& b){ return a.x==b.x && a.y==b.y; } friend bool operator< (const Point& a, const Point& b){ return a.x < b.x || (a.x == b.x && a.y < b.y); } friend bool operator!=(const Point& a, const Point& b) { return !(a==b); } friend bool operator<=(const Point& a, const Point& b) { return !(b (const Point& a, const Point& b) { return b=(const Point& a, const Point& b) { return !(a关注打赏