C17在业务代码中最好用的十个特性
作者:jinshang,腾讯WXG后台开发工程师
自从步入现代C时代开始,C语言标准形成了三年一个版本的惯例:C11标志着现代C的开端,C14在11的基础上查缺补漏,并未加入许多新特性,而C17作为C11后的第一个大版本,标志着现代C逐渐走向成熟。WXG编译器升级到gcc7。5已有一段时间,笔者所在项目组也已经将全部代码升级到C17。在使用了c17一年多之后,笔者总结了C17在业务代码中最好用的十个特性。
注1:本文只包含wxg的gcc7。5支持的特性,ExecutionPolicy,FileSystem等暂不支持的特性不包含在内。
注2:本文只包含应用于业务逻辑的特性,FoldExpression,MathematicalSpecialFunctions等适用于元编程和科学计算的特性并不包含。
笔者将这些特性大体上分为三类:语法糖、性能提升和类型系统语法糖
这里所说的语法糖,并不是严格意义上编程语言级别的语法糖,还包括一些能让代码更简洁更具有可读性的函数和库:结构化绑定
c17最便利的语法糖当属结构化绑定。结构化绑定是指将array、tuple或struct的成员绑定到一组变量上的语法,最常用的场景是在遍历mapunorderedmap时不用再声明一个中间变量了:prec17for(constautokv:map){constautokeykv。first;constautovaluekv。second;。。。}c17for(constauto〔key,value〕:map){。。。}
严格来说,结构化绑定的结果并不是变量,c标准称之为名字别名,这也导致它们不允许被lambda捕获,但是gcc并没有遵循c标准,所以以下代码在gcc可以编译,clang则编译不过for(constauto〔key,value〕:map){〔key,value〕{std::coutkey:valuestd::endl;}();}
在clang环境下,可以在lambda表达式捕获时显式引入一个引用变量通过编译for(constauto〔key,value〕:map){〔keykey,valuevalue〕{std::coutkey:valuestd::endl;}();}
另外这条限制在c20中已经被删除,所以在c20标准中gcc和clang都可以捕获结构化绑定的对象了。std::tuple的隐式推导
在c17以前,构造std::pairstd::tuple时必须指定数据类型或使用std::makepairstd::maketuple函数,c17为std::pairstd::tuple新增了推导规则,可以不再显示指定类型。prec17std::pairint,std::stringp1{3。14,pis};autop1std::makepair(3。14,pis);c17std::pairp3{3。14,pis};ifconstexpr
ifconstexpr语句是编译期的if判断语句,在C17以前做编译期的条件判断往往通过复杂SFINAE机制或模版重载实现,甚至嫌麻烦的时候直接放到运行时用if判断,造成性能损耗,ifconstexpr大大缓解了这个问题。比如我想实现一个函数将不同类型的输入转化为字符串,在c17之前需要写三个函数去实现,而c17只需要一个函数。prec17templatetypenameTstd::stringconvert(Tinput){returnstd::tostring(input);}constchar和string进行特殊处理std::stringconvert(constcharinput){returninput;}std::stringconvert(std::stringinput){returninput;}c17templatetypenameTstd::stringconvert(Tinput){ifconstexpr(std::issamevT,constcharstd::issamevT,std::string){returninput;}else{returnstd::tostring(input);}}if初始化语句
c17支持在if的判断语句之前增加一个初始化语句,将仅用于if语句内部的变量声明在if内,有助于提升代码的可读性。且对于lockiterator等涉及并发RAII的类型更容易保证程序的正确性。c17std::mapint,std::stringm;std::mutexmx;externboolsharedflag;guardedbymxintdemo(){if(autoitm。find(10);it!m。end()){returnitsecond。size();}if(charbuf〔10〕;std::fgets(buf,10,stdin)){m〔0〕buf;}if(std::lockguardlock(mx);sharedflag){unsafeping();sharedflagfalse;}if(ints;intcountReadBytesWithSignal(s)){publish(count);raise(s);}if(constautokeywords{if,for,while};std::ranges::anyof(keywords,〔tok〕(constcharkw){returntokkw;})){std::cerrTokenmustnotbeakeyword;}}性能提升std::sharedmutex
sharedmutex是c的原生读写锁实现,有共享和独占两种锁模式,适用于并发高的读场景下,通过reader之前共享锁来提升性能。在c17之前,只能自己通过独占锁和条件变量自己实现读写锁或使用c14加入的性能较差的std::sharedtimedmutex。以下是通过sharedmutex实现的线程安全计数器:c17classThreadSafeCounter{public:ThreadSafeCounter()default;Multiplethreadsreaderscanreadthecountersvalueatthesametime。unsignedintget()const{std::sharedlocklock(mutex);returnvalue;}Onlyonethreadwritercanincrementwritethecountersvalue。unsignedintincrement(){std::uniquelocklock(mutex);returnvalue;}Onlyonethreadwritercanresetwritethecountersvalue。voidreset(){std::uniquelocklock(mutex);value0;}private:mutablestd::sharedmutexmutex;unsignedintvalue0;};std::stringview
std::stringview顾名思义是字符串的视图,类成员变量包含两个部分:字符串指针和字符串长度,std::stringview涵盖了std::string的所有只读接口。std::stringview对字符串不具有所有权,且兼容std::string和constchar两种类型。
c17之前,我们处理只读字符串往往使用conststd::string,std::string有两点性能优势:兼容两种字符串类型,减少类型转换和内存分配。如果传入的是明文字符串constchar,conststd::string需要进行一次内存分配,将字符串拷贝到堆上,而std::stringview则可以避免。在处理子串时,std::string::substr也需要进行拷贝和分配内存,而std::stringview::substr则不需要,在处理大文件解析时,性能优势非常明显。fromhttps:stackoverflow。coma40129046author:PavelDavydovstringview的removeprefix比conststd::string的快了15倍stringremoveprefix(conststringstr){returnstr。substr(3);}stringviewremoveprefix(stringviewstr){str。removeprefix(3);returnstr;}staticvoidBMremoveprefixstring(benchmark::Statestate){std::stringexample{asfaghdfgsghasfasg3423rfgasdg};while(state。KeepRunning()){autoresremoveprefix(example);autoresremoveprefix(stringview(example));forstringviewif(res!aghdfgsghasfasg3423rfgasdg){throwstd::runtimeerror(badop);}}}std::mapunorderedmaptryemplace
在向std::mapunorderedmap中插入元素时,我们往往使用emplace,emplace的操作是如果元素key不存在,则插入该元素,否则不插入。但是在元素已存在时,emplace仍会构造一次待插入的元素,在判断不需要插入后,立即将该元素析构,因此进行了一次多余构造和析构操作。c17加入了tryemplace,避免了这个问题。同时tryemplace在参数列表中将key和value分开,因此进行原地构造的语法比emplace更加简洁std::mapstd::string,std::stringm;emplace的原地构造需要使用std::piecewiseconstruct,因为是直接插入std::pairkey,valuem。emplace(std::piecewiseconstruct,std::forwardastuple(c),std::forwardastuple(10,c));tryemplace可以直接原地构造,因为参数列表中key和value是分开的m。tryemplace(c,10,c)
同时,c17还给std::mapunorderedmap加入了insertorassign函数,可以更方便地实现插入或修改语义类型系统
c17进一步完备了c的类型系统,终于加入了众望所归的类型擦除容器(TypeErasure)和代数数据类型(AlgebraicDataType)std::any
std::any是一个可以存储任何可拷贝类型的容器,C语言中通常使用void实现类似的功能,与void相比,std::any具有两点优势:std::any更安全:在类型T被转换成void时,T的类型信息就已经丢失了,在转换回具体类型时程序无法判断当前的void的类型是否真的是T,容易带来安全隐患。而std::any会存储类型信息,std::anycast是一个安全的类型转换。std::any管理了对象的生命周期,在std::any析构时,会将存储的对象析构,而void则需要手动管理内存。
std::any应当很少是程序员的第一选择,在已知类型的情况下,std::optional,std::variant和继承都是比它更高效、更合理的选择。只有当对类型完全未知的情况下,才应当使用std::any,比如动态类型文本的解析或者业务逻辑的中间层信息传递。std::optional
std::optional代表一个可能存在的T值,对应Haskell中的Maybe和RustOCaml中的option,实际上是一种SumType。常用于可能失败的函数的返回值中,比如工厂函数。在C17之前,往往使用T作为返回值,如果为nullptr则代表函数失败,否则T指向了真正的返回值。但是这种写法模糊了所有权,函数的调用方无法确定是否应该接管T的内存管理,而且T可能为空的假设,如果忘记检查则会有SegFault的风险。prec17ReturnTypefunc(conststd::stringin){ReturnTyperetnewReturnType;if(in。size()0)returnnullptr;。。。returnret;}c17更安全和直观std::optionalReturnTypefunc(conststringin){ReturnTyperet;if(in。size()0)returnnullopt;。。。returnret;}std::variant
std::variantT,U,。。。代表一个多类型的容器,容器中的值是制定类型的一种,是通用的SumType,对应Rust的enum。是一种类型安全的union,所以也叫做taggedunion。与union相比有两点优势:可以存储复杂类型,而union只能直接存储基础的POD类型,对于如std::vector和std::string就等复杂类型则需要用户手动管理内存。类型安全,variant存储了内部的类型信息,所以可以进行安全的类型转换,c17之前往往通过unionenum来实现相同功能。
通过使用std::variantT,Err,用户可以实现类似Rust的std::result,即在函数执行成功时返回结果,在失败时返回错误信息,上文的例子则可以改成:std::variantReturnType,Errfunc(conststringin){ReturnTyperet;if(in。size()0)returnErr{inputisempty};。。。return{ret};}
需要注意的是,c17只提供了一个库级别的variant实现,没有对应的模式匹配(PatternMatching)机制,而最接近的std::visit又缺少编译器的优化支持,所以在c17中std::variant并不好用,跟Rust和函数式语言中出神入化的SumType还相去甚远,但是已经有许多围绕std::variant的提案被提交给c委员会探讨,包括模式匹配,std::expected等等。
总结一下,c17新增的三种类型给c带来了更现代更安全的类型系统,它们对应的使用场景是:std::any适用于之前使用void作为通用类型的场景。std::optional适用于之前使用nullptr代表失败状态的场景。std::variant适用于之前使用union的场景。总结
以上是笔者在生产环境中最常用的c17特性,除了本文描述的十个特性外,c17还添加了如lambda值捕获this,钳夹函数std::clamp(),强制检查返回值〔〔nodiscard〕〕等非常易用的特性,本文篇幅有限不做赘述,欢迎有兴趣的读者自行探索。
初二观后感金陵十三钗观后感七今天到电影院看了《金陵十三钗》,怎么说呢?给我留下一路的害怕感,我害怕我会遇到那样的状况,虽然我同时也在庆幸我是出生在解放后的年代,但是坐在哪里的我深切的体会到当年如我一般的女……
原以为美团月付是渣男,没想到是海王原创与归与归随笔收录于话题热点话题81文艺随笔44最近一段时期,几家互联网公司接连冲上热搜。但是我期待的美团月付,依旧声量不够。互联网给人们带来了诸多方便,但是一些……
致我们终将逝去的青春读后感原来大家的青春岁月可以这么的相近,本以为自己有很多话想说,可是坐在这却不知道说什么了。只知道那其中包括了太多我逝去的青春岁月。我们怀恋最多的不再是某个人,某件事,而是我们曾经那……
对标Model3,定位低于Taycan,保时捷将推出新款纯电各位车友,大家好!今天选车网为您带来保时捷的最新消息,请点击关注选车网,第一时间了解最新的汽车资讯。近日,选车网从相关渠道获悉,保时捷计划在Taycan和电动版的Maca……
中国企业之光!小米5G智能手机被德国电信认可,苹果却被排除在近日,德国电信官宣了好消息,在小米、高通、诺基亚、爱立信以及三星等供应商伙伴的合作之下,实现了5GVoNR通话测试。没有看错,小米就是唯一一家参与到这场测试中的中国企业,并且还……
鸿蒙OS到底有多强?对比苹果iOS数据出炉,华为这次真给力流畅!这是不少华为手机用户升级鸿蒙OS系统之后的第一感觉,伴随着6月2号鸿蒙OS系统正式发布,首批可适配鸿蒙OS系统的机型都能自行升级这一华为公司最新的自研系统,而根据不少用户……
描写我的家乡的400字作文武汉是我的家乡,那里有许多名胜古迹古琴台、长江、东湖。。。。。。,那儿还有许多特色小吃呢,有热干面,烧饼,汤包,豆皮。。。。。。。。首先,来介绍一下武汉的小吃吧!汤包的外……
华为回应手机厂商接入鸿蒙系统疑问疑似荣耀X20真机曝光厂商接入鸿蒙系统由自己选择Hello大家好,这里是科技V报,我是龙二Pro,昨晚,华为HarmonyOS2。0公测版正式发布,同时开启了首批公测的升级活动,包括华为Mat……
青春,游离于现实之外的故事天空飘着淡淡的愁绪,空气中也弥漫着些许伤感。楼下的那棵木槿繁华了一季也渐次零落。这是一个即将离别的季节,这是一个述说不舍与眷恋的季节。那些平日里觉得碍眼的地方,此刻也变得格外可……
优秀英语作文读书的好处王夫之说ldquo;夫读书将以何为哉?辨其大义,以修己治人之体也,察其微言,以善精义入神之用也。rdquo;那么读书的好处英语作文怎么写呢?读书的好处作文一ifwe……
描写热闹场景的小学作文热闹的场景是一种开心的氛围,身在其中的人都会这种热闹感染变得更加的开心,那么该怎么把热闹场景记录下来呢?下面是描写热闹场景的小学作文,一起来看看吧!描写热闹场景的小学作文【1】……
给姐姐的一封信3忧伤冰晶姐姐:你好!请你允许我这样的称呼你,我爷爷说了只要是比我大的都应该叫姐姐,苟且你可不是一般的姐姐,你是我的指导师姐。在我申请精品作文之中,你给予了我指点:《……
推荐春节小学生作文200字锦集6篇在平凡的学习、工作、生活中,大家或多或少都会接触过作文吧,借助作文人们可以实现文化交流的目的。怎么写作文才能避免踩雷呢?下面是小编为大家收集的春节小学生作文200字6篇,欢迎大……
小蜜蜂的初中作文蜜蜂的样子很稀奇,小脑袋像一颗小豆豆,原来蜜蜂也爱臭美,它们为了漂亮用一条小皮带把肚子绕起来,这让我突然想起了罗隐写的《蜂》不论平地与山尖,无限风光尽被占。采得百花成蜜后,为谁……
若梦如叶你不是我,怎知我乐;你不是我,岂动我悲;你不是我,何解我意;你不是我,安明我志。mdash;题记我是一片树叶,梦想着飞翔,他人笑我痴、嘲我傻,我处之泰然。抛开嘲笑,……
推荐三年级日记模板集锦6篇三年级日记篇1我们班有个小霸王,叫小牛(真名不敢透露)。他特别爱搞恶作剧,全班的同学全都在他的统治下瑟瑟发抖。有一天我去厕所大便,距离厕所还有20米左右时,遇到了小……
过中秋节的小学生作文(精选11篇)在学习、工作乃至生活中,大家对作文都不陌生吧,借助作文人们可以实现文化交流的目的。那要怎么写好作文呢?下面是小编为大家整理的过中秋节的小学生作文(精选11篇),仅供参考,大家一……
压力的议论文作文600字压力有很多种,有生活压力,学习的压力等,这些压力都来自于生活和社会。下面是小编为大家精心整理的关于压力的议论文作文600字,希望能够帮助到你们。压力,通往成功之桥世……
2020关于新型肺炎疫情的小学作文隔山,隔水,不隔爱!封城,封路,不封心!相信在我们全国人民的努力下,我们一定能攻克疫情!即将到来的春天一定会更美!下面是小编整理的2020关于新型肺炎疫情的小学作文,欢迎阅读参……
友情我的快乐之源作文550字友情与亲情的不同之处在于,它是无声无息的,完全凭着一种默契,一种感觉,一种心与心的体味,一种情与情的撞击。也可以说,友情是双方共建的,它可能在无声中升华,也可能在无声中消失,友……
关于黄山之行作文700字薄海内外,无如徽之黄山。登黄山,天下无山,观止矣!明朝著名地理学家、旅行家徐霞客曾这样赞美过黄山。黄山位于安徽省南部黄山市境内,是世界文化与自然双重遗产,世界地质公园,国家5A……
第一次单独坐公交车三年级作文那天放学后,妈妈打来电话:千寻,今天妈妈有事,不能来接你了。你可以自己坐公交车回家吗?我一口答应:好啊!其实,看到很多同学自己坐公交车回家,我早就想单独坐车回家了,只是妈……
我可以失败,但不可以被打败的初中生作文我可以失败,但不可以被打败坎坷的人生路上,谁没有过失败。如果没有失败,又怎会有成功呢?题记月考,在我们的抱怨下结束了。每考完一门课,同学们都七嘴八舌地议论,听……
小学作文感动的泪水泪一种饱含意义的液体,代表着人类的情感,它让世界变得美丽,也让我们变得怡然。一天的晚上,我在床上翻来覆去总是睡不觉,妈妈和我心灵相通似的,妈妈从床上下来,走来我的全身是汗……