范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

C17在业务代码中最好用的十个特性

  作者:jinshang,腾讯WXG后台开发工程师
  自从步入现代C++时代开始,C++语言标准形成了三年一个版本的惯例:C++11标志着现代C++的开端,C++14在11的基础上查缺补漏,并未加入许多新特性,而C++17作为C++11后的第一个大版本,标志着现代C++逐渐走向成熟。WXG编译器升级到gcc7.5已有一段时间,笔者所在项目组也已经将全部代码升级到C++17。在使用了c++17一年多之后,笔者总结了C++17在业务代码中最好用的十个特性。
  注1:本文只包含wxg的gcc7.5支持的特性,Execution Policy, File System等暂不支持的特性不包含在内。
  注2:本文只包含应用于业务逻辑的特性,Fold Expression, Mathematical Special Functions等适用于元编程和科学计算的特性并不包含。
  笔者将这些特性大体上分为三类:语法糖、性能提升和类型系统语法糖
  这里所说的语法糖,并不是严格意义上编程语言级别的语法糖,还包括一些能让代码更简洁更具有可读性的函数和库: 结构化绑定
  c++17最便利的语法糖当属结构化绑定。结构化绑定是指将array、tuple或struct的成员绑定到一组变量*上的语法,最常用的场景是在遍历map/unordered_map时不用再声明一个中间变量了: // pre c++17 for(const auto& kv: map){   const auto& key = kv.first;   const auto& value = kv.second;   // ... }  // c++17 for(const auto& [key, value]: map){   // ... }
  严格来说,结构化绑定的结果并不是变量,c++标准称之为名字/别名,这也导致它们不允许被lambda捕获,但是gcc并没有遵循c++标准,所以以下代码在gcc可以编译,clang则编译不过 for(const auto& [key, value]: map){     [&key, &value]{         std::cout << key << ": " << value << std::endl;     }(); }
  在clang环境下,可以在lambda表达式捕获时显式引入一个引用变量通过编译 for(const auto& [key, value]: map){     [&key = key, &value = value]{         std::cout << key << ": " << value << std::endl;     }(); }
  另外这条限制在c++20中已经被删除,所以在c++20标准中gcc和clang都可以捕获结构化绑定的对象了。 std::tuple的隐式推导
  在c++17以前,构造 std::pair/std::tuple 时必须指定数据类型或使用std::make_pair/std::make_tuple 函数,c++17为std::pair/std::tuple 新增了推导规则,可以不再显示指定类型。// pre c++17 std::pair p1{3.14, "pi"s}; auto p1 = std::make_pair(3.14, "pi"s);  // c++17 std::pair p3{3.14, "pi"s}; if constexpr
  if constexpr语句是编译期的if判断语句,在C++17以前做编译期的条件判断往往通过复杂SFINAE机制或模版重载实现,甚至嫌麻烦的时候直接放到运行时用if判断,造成性能损耗,if constexpr大大缓解了这个问题。比如我想实现一个函数将不同类型的输入转化为字符串,在c++17之前需要写三个函数去实现,而c++17只需要一个函数。 // pre c++17 template  std::string convert(T input){     return std::to_string(input); }  // const char*和string进行特殊处理 std::string convert(const char* input){     return input; } std::string convert(std::string input){     return input; } // c++17 template  std::string convert(T input) {     if constexpr (std::is_same_v ||                   std::is_same_v) {         return input;     } else {         return std::to_string(input);     } } if初始化语句
  c++17支持在if的判断语句之前增加一个初始化语句,将仅用于if语句内部的变量声明在if内,有助于提升代码的可读性。且对于lock/iterator等涉及并发/RAII的类型更容易保证程序的正确性。 // c++ 17 std::map m; std::mutex mx; extern bool shared_flag; // guarded by mx   int demo() {     if (auto it = m.find(10); it != m.end()) { return it->second.size(); }     if (char buf[10]; std::fgets(buf, 10, stdin)) { m[0] += buf; }     if (std::lock_guard lock(mx); shared_flag) { unsafe_ping(); shared_flag = false; }     if (int s; int count = ReadBytesWithSignal(&s)) { publish(count); raise(s); }     if (const auto keywords = {"if", "for", "while"};         std::ranges::any_of(keywords, [&tok](const char* kw) { return tok == kw; }))     {         std::cerr << "Token must not be a keyword ";     } } 性能提升std::shared_mutex
  shared_mutex 是c++的原生读写锁实现,有共享和独占两种锁模式,适用于并发高的读场景下,通过reader之前共享锁来提升性能。在c++17之前,只能自己通过独占锁和条件变量自己实现读写锁或使用c++14加入的性能较差的std::shared_timed_mutex 。以下是通过shared_mutex 实现的线程安全计数器:// c++17 class ThreadSafeCounter {  public:   ThreadSafeCounter() = default;     // Multiple threads/readers can read the counter"s value at the same time.   unsigned int get() const {     std::shared_lock lock(mutex_);     return value_;   }     // Only one thread/writer can increment/write the counter"s value.   unsigned int increment() {     std::unique_lock lock(mutex_);     return ++value_;   }     // Only one thread/writer can reset/write the counter"s value.   void reset() {     std::unique_lock lock(mutex_);     value_ = 0;   }    private:   mutable std::shared_mutex mutex_;   unsigned int value_ = 0; }; std::string_view
  std::string_view 顾名思义是字符串的"视图",类成员变量包含两个部分:字符串指针和字符串长度,std::string_view涵盖了std::string的所有只读接口。std::string_view对字符串不具有所有权,且兼容std::string和const char*两种类型。
  c++17之前,我们处理只读字符串往往使用 const std::string& ,std::string 有两点性能优势:兼容两种字符串类型,减少类型转换和内存分配。如果传入的是明文字符串 const char* , const std::string& 需要进行一次内存分配,将字符串拷贝到堆上,而std::string_view 则可以避免。在处理子串时, std::string::substr 也需要进行拷贝和分配内存,而std::string_view::substr 则不需要,在处理大文件解析时,性能优势非常明显。// from https://stackoverflow.com/a/40129046 // author: Pavel Davydov  // string_view的remove_prefix比const std::string&的快了15倍 string remove_prefix(const string &str) {   return str.substr(3); } string_view remove_prefix(string_view str) {   str.remove_prefix(3);   return str; }  static void BM_remove_prefix_string(benchmark::State& state) {                   std::string example{"asfaghdfgsghasfasg3423rfgasdg"};   while (state.KeepRunning()) {     auto res = remove_prefix(example);     // auto res = remove_prefix(string_view(example)); for string_view     if (res != "aghdfgsghasfasg3423rfgasdg") {       throw std::runtime_error("bad op");     }   } } std::map/unordered_map try_emplace
  在向 std::map/unordered_map 中插入元素时,我们往往使用emplace ,emplace 的操作是如果元素key不存在,则插入该元素,否则不插入。但是在元素已存在时,emplace 仍会构造一次待插入的元素,在判断不需要插入后,立即将该元素析构,因此进行了一次多余构造和析构操作。c++17加入了try_emplace ,避免了这个问题。同时try_emplace在参数列表中将key和value分开,因此进行原地构造的语法比emplace 更加简洁std::map m; // emplace的原地构造需要使用std::piecewise_construct,因为是直接插入std::pair m.emplace(std::piecewise_construct,            std::forward_as_tuple("c"),            std::forward_as_tuple(10, "c"));  // try_emplace可以直接原地构造,因为参数列表中key和value是分开的 m.try_emplace("c", 10, "c")
  同时,c++17还给 std::map/unordered_map 加入了insert_or_assign 函数,可以更方便地实现插入或修改语义类型系统
  c++17进一步完备了c++的类型系统,终于加入了众望所归的类型擦除容器(Type Erasure)和代数数据类型(Algebraic Data Type) std::any
  std::any 是一个可以存储任何可拷贝类型的容器,C语言中通常使用void* 实现类似的功能,与void* 相比,std::any 具有两点优势:std::any 更安全:在类型T被转换成void* 时,T的类型信息就已经丢失了,在转换回具体类型时程序无法判断当前的void* 的类型是否真的是T,容易带来安全隐患。而std::any 会存储类型信息,std::any_cast 是一个安全的类型转换。std::any 管理了对象的生命周期,在std::any 析构时,会将存储的对象析构,而void* 则需要手动管理内存。
  std::any 应当很少是程序员的第一选择,在已知类型的情况下,std::optional , std::variant 和继承都是比它更高效、更合理的选择。只有当对类型完全未知的情况下,才应当使用std::any ,比如动态类型文本的解析或者业务逻辑的中间层信息传递。std::optional
  std::optional 代表一个可能存在的T值,对应Haskell中的Maybe 和Rust/OCaml中的option ,实际上是一种Sum Type。常用于可能失败的函数的返回值中,比如工厂函数。在C++17之前,往往使用T* 作为返回值,如果为nullptr 则代表函数失败,否则T* 指向了真正的返回值。但是这种写法模糊了所有权,函数的调用方无法确定是否应该接管T* 的内存管理,而且T* 可能为空的假设,如果忘记检查则会有SegFault的风险。// pre c++17 ReturnType* func(const std::string& in) {     ReturnType* ret = new ReturnType;     if (in.size() == 0)         return nullptr;     // ...     return ret; }  // c++17 更安全和直观 std::optional func(const string& in) {     ReturnType ret;     if (in.size() == 0)         return nullopt;     // ...     return ret; } std::variant
  std::variant 代表一个多类型的容器,容器中的值是制定类型的一种,是通用的Sum Type,对应Rust的enum 。是一种类型安全的union ,所以也叫做tagged union 。与union 相比有两点优势:可以存储复杂类型,而union只能直接存储基础的POD类型,对于如 std::vector 和std::string 就等复杂类型则需要用户手动管理内存。类型安全,variant存储了内部的类型信息,所以可以进行安全的类型转换,c++17之前往往通过 union +enum 来实现相同功能。
  通过使用 std::variant ,用户可以实现类似Rust的std::result ,即在函数执行成功时返回结果,在失败时返回错误信息,上文的例子则可以改成:std::variant func(const string& in) {     ReturnType ret;     if (in.size() == 0)         return Err{"input is empty"};     // ...     return {ret}; }
  需要注意的是,c++17只提供了一个库级别的variant实现,没有对应的模式匹配(Pattern Matching)机制,而最接近的 std::visit 又缺少编译器的优化支持,所以在c++17中std::variant 并不好用,跟Rust和函数式语言中出神入化的Sum Type还相去甚远,但是已经有许多围绕std::variant 的提案被提交给c++委员会探讨,包括模式匹配,std::expected 等等。
  总结一下,c++17新增的三种类型给c++带来了更现代更安全的类型系统,它们对应的使用场景是: std::any 适用于之前使用void* 作为通用类型的场景。std::optional 适用于之前使用nullptr 代表失败状态的场景。std::variant 适用于之前使用union 的场景。总结
  以上是笔者在生产环境中最常用的c++17特性,除了本文描述的十个特性外,c++17还添加了如lambda值捕获*this, 钳夹函数std::clamp(), 强制检查返回值[[nodiscard]]等非常易用的特性,本文篇幅有限不做赘述,欢迎有兴趣的读者自行探索。

60倍变焦7英寸E5材质,首发5999元,大屏旗舰几乎零差评声明原创不易,禁止搬运,违者必究!对于手机产品来说,在刚上市的时候,消费者会从多个方面,来看这部产品的表现到底如何。现在的手机市场,竞争又是激烈的,所以手机厂商也致力于在手机产品上八曲面AG玻璃旗舰降价!18G640G,5600毫安大电池一说到游戏手机产品,现在虽然国内不少厂商在做这方面的市场,但是不得不说,目前还是联想在这方面的产品做的最为吸引人,不过现阶段的联想在国内市场上也确实不好过。不过作为一家老手机厂商,说实话,三星官翻机Note20Ultra还值不值得买?两个月前发售了三星S22系列,这不仅引来了粉丝们的购机欲望,更是在手机圈里掀起了大震动。当然也有不少粉丝,坚定自己的想法,在这个时候毅然选择了三星官翻机Note20U,那今天就一起3000不到的iPhone12,有什么不买它的理由吗?咱先直接说说价格,像在某东等第三方平台全新iPhone12的128G已经降到4400多就能入手而参考转转等二手平台二手目前最低是在2900多,比原价低了超过3K了。怎么说呢,价格真米家冰箱对开门502L冰晶版开启预售一天不到一度电4月25日,米家冰箱对开门502L冰晶版正式开启预售。该冰箱采用全新升级的面板,简约珍珠白配色放在家中仿佛艺术品冰箱采用高效双变频,一天不到一度电,运行噪音低至37分贝而502L大三恒系统真的要全面取代空调了吗?凭什么?上世纪80年代以来,三恒系统(毛细管辐射空调系统)就得到了欧洲德国法国等国家的推荐,打破传统空调理论,使用更舒适节能的毛细管辐射空调系统取而代之。三恒系统源于欧洲的技术中国人制造空开学如期而至,小白智能相机ampampamp小白智能门铃紧密相随时光如白驹过隙,转眼间又到了开学的季节,小朋友们纷纷踏上了上学的路程。真的可以说是有人欢喜有人愁。欢喜之处在于终于不用照看家里的神兽了,忧愁之处在于无人照看的神兽,过的究竟如何呢?国办创新消费业态和模式培育壮大智慧产品和智慧零售等消费新业态国务院办公厅发布关于进一步释放消费潜力促进消费持续恢复的意见。意见提出,创新消费业态和模式。适应常态化疫情防控需要,促进新型消费,加快线上线下消费有机融合,扩大升级信息消费,培育壮如何评价小米12?感谢您的阅读!小米12手机,确实是目前来说在握持体验上表现不错的一款机型。这款手机它主打的就是握持,这款手机采用的是6。28英寸的屏幕,69。9mm的宽度,整款手机所拥有的综合表现苹果最爱用的圆角矩形,放在PPT中,谁用谁好看大家看新闻了吗?继刘强东卸任京东董事长之后,张勇也宣布卸任阿里巴巴董事长了!而说起阿里巴巴,我就想起来了,昨天我无意间点开淘宝官网的时候,看到他家的网页设计,真的是丑爆了!点开之后华为Mate50Pro规格曝光,最快6月发布,最低6499元有戏由于众所周知的原因,华为已经接受了三轮的打击,这也导致了华为的手机业务严重下滑,所以余承东必须在接下来的时间里拿出一款更具说服力的产品,诸多的花粉也希望华为能够重新王者归来。前不久
8月份币圈的牛市要开启了吗?牛市一般指长期上涨的行情,问题是这个长期具体指的是多久呢?以年为单位来看比特币的增长,现在依然算是周期内的牛市,519之后的下跌也只不过是小调整。因为世界上存在大量的去中心化交易需纯电商正在加速死亡!新零售正在快速崛起!这是个必须快跑的时代618的到来,让全民积攒了大半年的购物欲瞬间爆发,电商正在深度改变着消费格局,网络购物成为了我们新的生活习惯和消费模式。但也有人表示现在的电商购物模式,让自己有了疲惫感,生活中的仪特斯拉Cybertruck延迟至2022年交付外媒或失去先发优势环球网科技综合报道8月9日消息,据外媒指出,特斯拉旗下电动皮卡Cybertruck的交付时间将由原定的2021年底推迟至2022年,目前国内海外特斯拉官网的Cybertruck页面几分钟收三百多条短信,只因挂了一通电话?运营商回应解决办法8月2号一早,贵阳的沈先生在开会时接到了一个陌生的电话,没想到挂了电话后,他就遭到了莫名的骚扰。沈先生电话一挂,可能就一两分钟的间隔,就开始直接刷短信给我。现在已经是370多条,一北汽蓝谷以技术立本,向科技转型自2009年成立以来,北汽新能源凭借在纯电动汽车领域的前瞻布局和技术积累,推出了系列高性价比产品,为中国纯电动汽车的市场培育做了积极的贡献,同时获得了较大的市场份额。然而,2020警方介入调查阿里女员工被侵害警情,这些法律问题了解一下一则女员工自曝被侵害事件,让阿里巴巴深陷舆论漩涡。近日,某认证信息为阿里巴巴员工的人士在某平台爆料称,某女同事在公务出差过程中被灌醉,后遭到商户猥亵,当晚该女同事被其上司侵犯。该女比亚迪硬钢特斯拉,推出506km续航汉EV,售价仅20。98万上个月底(7月30日),特斯拉又降价了,Model3标准续航后驱升级版从25。09万元降低至23。59万元,足足降低了1。5万元。除了众韭菜们痛心疾首,也有舆论为中国的新能源汽车企被阿里开除的涉嫌侵害男员工事件曝出前就计划跳槽字节跳动已终止其招聘流程21世纪经济报道记者白杨北京报道针对阿里巴巴女员工被侵害一事,8月9日凌晨,阿里巴巴董事会主席兼CEO张勇在集团内部公布了阶段性调查结果和处理决定,其中包括将涉嫌男员工辞退,永不录联想最难买的游戏本新品新拯救者Y9000K上市,11999元有价无货各位想买游戏本的小伙伴,今年有没有被联想拯救者给劝退呢?虽然产品相当不错,但热门产品一个比一个难买,而那些不太难买的产品,似乎他们和其他品牌对比起来竞争力又不太强,这也导致了今年拯元宇宙火爆全球,对分布式存储FIL有什么利好?近日,元宇宙火爆全球。无论是Facebook宣布重金投资数十亿美金开发元宇宙,还是腾讯大力推进元宇宙业务,这个新鲜词慢慢被大众所知,并引发了社会各界的强烈关注。那么,到底什么是元宇疫情中遭遇网约车坐地起价,职业道德岂容开倒车来源半月谈半月谈评论员李力可薛晨近日,一名乘客到达成都双流机场后,在使用滴滴App打车回家之时却遭遇一件糟心事接单的网约车司机在该乘客上车后取消订单,原本App显示预估价格23元的