您当前的位置: 首页 >  c++

10分钟速览 C++20 新增特性

发布时间:2021-01-26 17:32:10 ,浏览量:6

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)]]
    • 位运算
    • 一些小更新
  • 参考资料
新增关键字(keywords)
  • concept
  • requires
  • constinit
  • consteval
  • co_await
  • co_return
  • co_yield
  • char8_t
新增标识符(Identifies)
  • import
  • module
模块(Modules) 优点
  • 没有头文件
  • 声明实现仍然可分离, 但非必要
  • 可以显式指定那些导出(类, 函数等)
  • 不需要头文件重复引入宏 (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

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累加遍历的时候发生
协程(Coroutines) 什么是协程
  • 它是一个函数
  • 具备如下关键字之一:
    • co_wait: 挂起协程, 等待其它计算完成
    • co_return: 从协程返回 (协程 return 禁止使用)
    • co_yield: 同 python yield, 弹出一个值, 挂起协程, 下一次调用继续协程的运行
    • for co_await 循环体
for co_await (for-range-declaration: expression) statement

用处

  • 简化如下问题的实现:
    • generator
    • 异步I/O
    • 延迟计算
    • 事件驱动的程序
例子(VC++)
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 表达式

可以在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
constexpr string & vector
  • std::string 和 std::vector 类型现在可以作为 constexpr
  • 未来需要支持 constexpr 反射
原子(Atomic)智能指针
  • 智能指针(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
std::thread 在析构函数中如果线程  joinable()  会直接调用  std::terminate()  直接导致程序退出
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)
    • 多个阶段
    • 每个阶段中
      • 一个参与者运行至屏障点时被阻塞,需要等待其他参与者都到达屏障点, 当到达线程数达标之后
      • 阶段完成的回调将被执行
      • 线程计数器被重置
      • 开启下一阶段
      • 线程得以继续执行
std::atomic_ref
  • 头文件
  • Atomic 引用
  • 通过引用访问变为原子操作, 被引用对象可以为非原子类型
其他更新 指定初始化(Designated Initializers)
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            
关注
打赏
1688896170
查看更多评论

暂无认证

  • 6浏览

    0关注

    115984博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文
立即登录/注册

微信扫码登录

0.0953s