status
Published
slug
modern-cpp-3
type
Post
category
Technology
date
May 18, 2023
tags
C++
笔记
summary
第31-42章节的重点知识记录
第31章 属性说明符和标准属性(C++11~C++20)第32章 新增预处理器和宏(C++17 C++20)第33章 协程(C++20)第34章 基础特性的其他优化(C++11~C++20)第35章 可变参数模板(C++11 C++17 C++20)第36章 typename优化(C++17 C++20)第37章 模板参数优化(C++11 C++17 C++20)第38章 类模板的模板实参推导第39章 用户自定义推导指引(C++17)第40章 SFINAE(C++11)第41章 概念和约束(C++20)第42章 模板特性的其他优化(C++11 C++14)
第31章 属性说明符和标准属性(C++11~C++20)
- GCC属性语法:能够用于结构体、类、联合类型、枚举类型、变量或者函数
- 放置位置的不同,作用的效果也不同
- 放置在用户定义类型开始处的属性是声明类型的变量,而非类型本身
- 放置在class关键字或者整个类声明之后的属性声明的是类型本身
- 放置在声明对象之后的属性声明的是对象本身
- MSVC属性语法:
- 将__declspec放置在声明对象语句的开头,则属性描述的是对象本身
- 将__declspec放置在class和类型名之间,描述的则是类型
- C++11标准的属性说明符语法:双中括号开头,反双中括号结尾
- 属性说明符可以用在C++程序中的几乎所有地方
- 标准属性
- 9种标准属性
- noreturn:C++11引入,用于声明函数不会返回,即声明函数之后的程序执行流中断,函数不会返回到其调用者
- carries_dependency:C++11引入,允许跨函数传递内存依赖性,它通常用于弱内存顺序架构平台上多线程程序的优化,避免编译器生成不必要的内存栅栏指令
- 作为函数或者lambda表达式参数的属性出现,这种情况表示调用者不用担心内存顺序,函数内部会处理好这个问题,编译器可以不生成内存栅栏指令。
- 作为函数的属性出现,这种情况表示函数的返回值已经处理好内存顺序,不需要编译器在函数返回前插入内存栅栏指令。
- deprecated:C++14引入,带有此属性的实体被声明为弃用,但依然可以在代码中使用
- fallthrough:C++17引入,该属性可以在switch语句的上下文中提示编译器直落行为是有意的,并不需要给出警告
- fallthrough属性必须出现在case或者default标签之前
- nodiscard:C++17引入,该属性声明函数的返回值不应该被舍弃,否则鼓励编译器给出警告提示
- nodiscard属性只适用于返回值类型的函数
- 防止资源泄露,对于像malloc或者new这样的函数或者运算符,它们返回的内存指针是需要及时释放的,可以使用nodiscard属性提示调用者不要忽略返回值。
- 对于工厂函数而言,真正有意义的是回返的对象而不是工厂函数,将nodiscard属性应用在工厂函数中也可以提示调用者别忘了使用对象,否则程序什么也不会做。
- 对于返回值会影响程序运行流程的函数而言,nodiscard属性也是相当合适的,它告诉调用方其返回值应该用于控制后续的流程。
- maybe_unused:C++17引入,该属性声明实体可能不会被应用以消除编译器警告
- likely和unlikely:C++20引入,声明在标签或语句上,likely属性允许编译器对该属性所在的执行路径相对于其他执行路径进行优化;而unlikely属性恰恰相反
- no_unique_address:C++20引入,该属性指示编译器该数据成员不需要唯一的地址,也就是说它不需要与其他非静态数据成员使用不同的地址
- 两个对象都带有no_unique_address属性时,类型不同共用同一地址,类型相同时,无法使用同一地址
该属性可以出现在两种情况中
nodiscard属性有几个常用的场合
第32章 新增预处理器和宏(C++17 C++20)
特性测试宏,让程序员可以根据客户端开发环境适配不同的功能代码,让自己的代码库能够高效地应用在更多的环境上。
- 预处理器__has_include:用于判断某个头文件是否能被包含进去
- 语法:可以被包含,表达式求值为1,反之为0,__has_include并不关心头文件是否已经被包含进来。
- 特性测试宏:C++20标准添加,用于帮助我们测试当前的编译环境对各种功能特性的支持程度
- 属性特性测试宏(__has_cpp_attribute):指示编译环境是否支持某种属性,该属性可以是标准属性,也可以是编译环境厂商特有的属性
- 语言功能特性测试宏:宏代表编译环境所支持的语言功能特性,每个宏将被展开为该特性添加到标准时的年份和月份。
- 标准库功能特性测试宏:宏代表编译环境所支持的标准库功能特性,每个宏将被展开为该特性添加到标准时的年份和月份
- 新增宏__VA_OPT__:C++20引入,令可变参数宏更易于在可变参数为空的情况下使用
第33章 协程(C++20)
- 协程:具有以下任意一个关键字的函数就是协程,main函数不能作为协程
- co_await:可以创建一个挂起点将协程挂起并等待协程恢复
- 需要实现await_resume、await_ready和await_suspend,3个成员函数;
- 具备这3个函数的对象可以称为等待器
- co_yield
- promise_type类型:用于自定义协程自身行为
- co_return
可等待体:指该对象是可以被等待的
第34章 基础特性的其他优化(C++11~C++20)
- 显式自定义类型转换运算符
- 自定义类型转换符operator bool():特定条件下编译器会做隐式的自定义类型转换
- C++11标准将explicit引入自定义类型转换中,称为显式自定义类型转换
- std::launder()
- C++标准规定:如果新的对象在已被某个对象占用的内存上进行构建,那么原始对象的指针、引用以及对象名都会自动转向新的对象,除非对象是一个常量类型或对象中有常量数据成员或者引用类型
- 目的:防止编译器追踪到数据的来源以阻止编译器对数据的优化
- 返回值优化(C++11~C++17)
- 一种编译优化技术,它允许编译器将函数返回的对象直接构造到它们本来要存储的变量空间中而不产生临时对象
- 在C++11标准中,这种优化技术称为复制消除(copy elision)
- 分为:
- RVO(Return Value Optimization):返回语句的操作数为临时对象
- NRVO(Named Return Value Optimization):返回语句的操作数为具名对象
- C++14标准对返回值优化做了进一步的规定,规定中明确了对于常量表达式和常量初始化而言,编译器应该保证RVO,但是禁止NRVO。
- 在C++17标准中提到了确保复制消除的新特性
- 该特性指出,在传递临时对象或者从函数返回临时对象的情况下,编译器应该省略对象的复制和移动构造函数,即使这些复制和移动构造还有一些额外的作用,最终还是直接将对象构造到目标的存储变量上,从而避免临时对象的产生
- 允许按值进行默认比较(C++20)
- C++20标准规定类的默认比较运算符函数可以是一个参数为const C&的非静态成员函数,或是两个参数为const C&或C的友元函数
- 允许按值进行默认比较
- 支持new表达式推导数组长度(C++20)
- 允许数组转换为未知范围的数组(C++20)
- 在delete运算符函数中析构对象(C++20)
- 在C++20标准以前,对象析构和释放内存的操作完全由编译器控制
- C++20标准开始,用户可以分解析构和释放内存的操作
- 调用伪析构函数结束对象声明周期(C++20)
- 规定伪析构函数的调用总是会结束对象的生命周期
- 过去,调用伪析构函数会根据对象的不同执行不同的行为
- 修复const和默认复制构造函数不匹配造成无法编译的问题(C++20)
- 不推荐使用volatile的情况(C++20)
- volatile关键字:用于表达易失性,能够让编译器不要对代码做过多的优化,保证数据的加载和存储操作被多次执行
- 不推荐在下标表达式中使用逗号运算符(C++20)
- 逗号运算符:可以让多个表达式按照从左往右的顺序进行计算,整体的结果为系列中最后一个表达式的值
- 模块:C++20标准引入,将大型工程中的代码拆分成独立的逻辑单元,方便代码管理
- 模块能大大减少使用头文件导致的宏和函数重定义等问题
- 模块能大幅提升编译效率
第35章 可变参数模板(C++11 C++17 C++20)
- 可变参数模板:C++11标准引入
- 形参包展开
- 函数模板推导的匹配顺序:在推导的形参同时满足定参函数模板和可变参数函数模板的时候,编译器将优先选择定参函数模板,因为它比可变参数函数模板更加精确
- sizeof…运算符:专门针对形参包引入的新运算符,目的是获取形参包中形参的个数,返回的类型是std::size_t
- 可变参数模板的递归问题
- 折叠表达式:
- 折叠形参包:(args +…)
- C++17标准中有四种折叠规则:
- 一元向左折叠
- 一元向右折叠
- 二元向左折叠
- 二元向右折叠
- using声明中的包展开
- lambda表达式初始化捕获的包展开
第36章 typename优化(C++17 C++20)
- typename:在C++17标准前,必须使用class声明模板形参,C++17之后,允许使用typename声明模板形参
- 减少typename使用的必要性:在C++20标准中,增加了一些情况可以让我们省略typename关键字
第37章 模板参数优化(C++11 C++17 C++20)
- 允许常量求值作为所有非类型模板的实参
- 允许局部和匿名类型作为模板实参
- 允许函数模板的默认模板参数
- 函数模板添加到ADL查找规则
- 允许非类型模板形参中的字面量类类型
- 扩展的模板参数匹配规则
第38章 类模板的模板实参推导
- 通过初始化构造推导类模板的模板实参
- 类模板的模板实参推导过程中:拷贝初始化优先
- 使用列表初始化的时候,当且仅当初始化列表中只有一个与目标类模板相同的元素才会触发拷贝初始化
- lambda类型
- 别名模板的类模板实参推导
- 聚合类型的类模板实参推导
第39章 用户自定义推导指引(C++17)
- 使用自定义推导指引推导模板实例
- 用户自定义推导指引
- 聚合类型类模板的推导指引
第40章 SFINAE(C++11)
- SFINAE(Substitution Failure Is Not An Error,替换失败不是错误):指在函数模板重载时,当模板形参替换为指定的实参或由函数实参推导出模板形参的过程中出现了失败,则放弃这个重载而不是抛出一个编译失败
第41章 概念和约束(C++20)
概念和约束的推行能够很好地补充C++的类型检查机制
- std::enable_if约束模板:SFINAE规则使该模板元函数能辅助模板的开发者限定实例化模板的模板实参类型
- concept(概念):概念是对C++核心语言特性中模板功能的扩展。它在编译时进行评估,对类模板、函数模板以及类模板的成员函数进行约束:它限制了能被接受为模板形参的实参集。
- 使用concept和约束表达式定义概念
- requires子句和约束检查顺序
- 原子约束:表达式和表达式中模板形参到模板实参映射的组合(简称为形参映射)。
- requires表达式:requires关键字除了可以引入requires子句,还可以用来定义一个requires表达式,该表达式同样是一个纯右值表达式,表达式为true时表示满足约束条件,反之false表示不满足约束条件。
- requires表达式中,要求序列的分类:
- 简单要求:以requires关键字开始的要求,它只断言表达式的有效性,并不做表达式的求值操作
- 类型要求:以typename关键字开始的要求,紧跟typename的是一个类型名,通常可以用来检查嵌套类型、类模板以及别名模板特化的有效性
- 复合要求
- 嵌套要求:以requires开始的要求,它通常根据局部形参来指定其他额外的要求
- 约束可变参数模板
- 约束类模板特化
- 约束auto
第42章 模板特性的其他优化(C++11 C++14)
- 外部模板:
- 在多份源代码中定义同一个变量会让链接报错,因此需要使用extern来声明外部变量
- 连续右尖括号的解析优化(C++11)
- 在C++11标准中,编译器不再一味地使用贪婪原则将连续的两个右尖括号解析为右移,它会识别左尖括号激活状态并且将右尖括号优先匹配给激活的左尖括号
- friend声明模板形参(C++11)
- 一个类的友元可以忽略该类的访问属性(public、protected、private),对类成员进行直接访问,破坏了代码的封装性。
- 变量模板(C++14):C++14的标准中引入了变量模板的特性,有了变量模板,我们不再需要冗余地定义类模板和函数模板,只需要专注要定义的变量即可
- explicit(bool):C++20标准扩展了explicit说明符的功能,它可以接受一个求值类型为bool的常量表达式,用于指定explicit的功能是否生效