IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 《Modern Effective C++》学习笔记5 右值引用,移动语义和完美转发 -> 正文阅读

[C++知识库]《Modern Effective C++》学习笔记5 右值引用,移动语义和完美转发

第五章:右值引用,移动语义和完美转发

移动语义:提供了比复制操作代价低得多的移动操作,提升了代码的性能;

完美转发:使得人们可以撰写接受任意实参的函数模板,并将其转发到其他函数,目标函数会接受到与转发函数所接受的完全相同的实参。

条款二十三:理解std::move和std::forward

这两个函数看起来是函数,其实它们在运行期间什么也不做,也不生成任何可执行代码。

std::move并不进行任何移动,它只是无条件地将实参强制转换成右值。

std::forward也不进行任何转发,它只是在某个特定条件下才执行右值强制转换,具体条件见下面代码:

?// 处理左值
?void process(const int& lvalArg) {
? ?std::cout << "lvalue process" << std::endl;
?}
?// 处理右值
?void process(int&& rvalArg) {
? ?std::cout << "rvalue process" << std::endl;
?}
??
?template<typename T>
?void logAndProcess(T&& param) {
? ?std::cout << "log here" << std::endl;
? ?// 函数形参都是左值,只有加上forward才能保持右值的类型
? ?process(std::forward<T>(param));
?}
??
?int main() {
? ?int a = 0;
? ?logAndProcess(std::move(a));
?}   // 输出"rvalue process"

条款二十四:区分万能引用和右值引用

T&&?

T&&会有两种含义,一种是右值引用,一种是万能引用。

右值引用:仅会绑定到右值,主要用于识别一个可移对象。

万能引用:可以绑定到几乎任何类型

怎么找万能引用?

万能引用通常在两个场景下现身

  1. 函数模板的形参

    ?template<typename T>
    ?void f(T&& param);
  2. auto声明

    ?auto&& var2 = var1;

以上两种场景有个共同的特点,他们都涉及类型推导。除了涉及类型推导之外,声明的形式必须是“T&&”这种格式才会是万能引用,所以以下代码都不符合条件:

?// 不是严格的T&&格式
?template<typename T>
?void f(std::vector<T>&& param);
??
?// const 同样改变了格式
?template<typename T>
?void f(const T&& param);
??
?// 特化的时候x的类型已经确定了
?template<class T, class Allocator = allocator<T>>
?class vector {
?public:
? ?void push_back(T&& x)
?};

反而是push_back的近亲emplace_back反而符合要求

?template<class T, class Allocator = allocator<T>>
?class vector {
?public:
? ?template<class... Args>
? ?void emplace_back(Args&&... args);
?};

条款二十五:针对右值引用实施std::move,针对万能引用实施std::forward

如果函数中多次使用到右值引用或者万能引用,而你想保证完成该对象的所有操作之前,其值不能被移走,就得在最后一次使用该引用时,才对其使用std::move或者std::forward。

虽然std::move和std::forward这么好,但是它们也有可能会阻止返回值优化这一机制。

返回值优化(RVO)

返回值优化就是可以将变量直接构建在返回值的内存上,避免返回时的复制操作,例如以下代码:

?Widget makeWidget() {
? ?Widget w;
? ?return w;
?}

支持返回值优化要符合以下两个条件:

  1. 局部对象类型与返回值类型相同;

  2. 返回的就是局部对象本身;

而在返回时添加std::move就会破坏这个条件,导致组织了返回值优化。

条款二十六:避免依万能引用型别进行重载

由于万能引用的优先级比隐式转换后匹配的优先级高,故如果使用万能引用进行重载,可能会优先调用万能引用版本的函数,造成意外的结果。

完美转发构造函数的问题尤其严重,因为对于非常量左值类型而言,它们一般都比复制构造函数更优先匹配到,而且也比基类中的复制和移动构造函数优先级高。

条款二十七:熟悉依万能引用型别进行重载的替代方案

  1. 舍弃重载,改成不同函数名;

  2. 回归C++98,放弃万能引用,使用const T&形参,就是会导致复制操作,在效率上有点差;

  3. 按值传递,参考条款四十一;

  4. 标签分派,即在对外函数的实际实现函数参数中加上std::true_type或std::false_type这样的标签,调用函数时通过传入的标签选择使用的函数版本;

  5. 对接受万能引用的模板施加限制,使用std::enable_if启动万能引用模板,例如下面例子只对Person相关参数才能通过编译

?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if<
? ? ? ? ? ? ? ? ? ?// 判断类型是否相同
? ? ? ? ? ? ? ? ? !std::is_same<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay<T>::type
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >::type
? ?>
? ?explicit Person(T&& n);
?};
??
?// 支持基类版本
?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if<
? ? ? ? ? ? ? ? ? ?// 判断是否是继承关系
? ? ? ? ? ? ? ? ? !std::is_base_of<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay<T>::type
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >::type
? ?>
? ?explicit Person(T&& n);
?};
??
?// C++14版本
?class Person {
?public:
? ?template<
? ? ?teypename T,
? ? ? ? ? ? ? ? ?// 打开开关
? ? ?typename = typename std::enable_if_t<
? ? ? ? ? ? ? ? ? ?// 判断是否是继承关系
? ? ? ? ? ? ? ? ? !std::is_base_of<Person,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 去掉引用,const,volatile等修饰词
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? typename std::decay_t<T>
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?>::value
? ? ? ? ? ? ? ? >
? ?>
? ?explicit Person(T&& n);
?};

万能引用形参在性能方面具有优势,但是在易用性方面存在劣势,需要根据实际情况进行权衡。

条款二十八:理解引用折叠

什么是引用折叠?

其实就是出现引用的引用的时候的一种处理方式,将引用的引用变成单个引用。

引用折叠的判断规则?

如果引用的引用这里两个引用中有任一引用为左值引用,结果就是左值引用,否则为右值引用。

引用折叠发生的语境

  1. 模板实例化;

  2. auto类型的生成;

  3. 创建和运用typedef和别名声明;

  4. decltype;

注:万能引用就是在类型推导过程中会区分左值和右值,以及会发生引用折叠的语境中的右值引用。

条款二十九:假定移动操作不存在,成本高,未使用

不存在?

并不是所有的类型都支持移动操作,这样移动操作就变成了复制操作。

成本高?

对于std::array,15个字符以内的std::string等对象,由于数据直接内置了,移动操作依然需要复制数据,并不能更快。

未使用?

移动本可以发生的语境下,要求移动操作不可以抛出异常,但是该函数未加上noexcept声明,导致编译器还是选择了复制操作。

对于以上三种情况来看,只有对于类型或移动语义的支持情况已知的代码,才适合使用移动操作。

条款三十:熟悉完美转发的失败类型

什么是完美转发失败?

对于基础函数f和完美转发函数fwd,如果f(expressin)和fwd(expression)执行了不同的操作或者fwd推导失败的情形。

完美转发失败的类型

  1. 大括号初始化,例如以下代码:

?void f(const std::vector<int>& v);
??
?template<typename... Ts>
?void fwd(Ts&&... params) {
? ?f(std::forward<Ts>(param)...);
?}
??
?// 因为{}不是一个具体类型,所以编译器会被禁止在fwd的调用过程中从表达式中推导类型
?fwd({1,2,3});   // 错误,无法编译
??
?// 因为auto可以进行推导类型,所以如下修改
?auto i1 = {1,2,3};
?fwd(i1);    // 通过编译
  1. 0和NULL用作空指针

0和NULL传递时一般会被推导成整形,应该使用nullptr。

  1. 仅有声明的整形static const成员变量

?// 对于类中的整形static const变量可以只声明不定义
?class Widget {
? public:
? ?static const std::size_t MinVals = 28;    // 给出了MinVals的声明
?  ...
?};
?...     // 未给出MinVals的定义
??
?void f(std::size_t val);
??
?f(Widget::MinVals); // 没问题,当f(28)处理
?// 引用当指针处理的,需要有某块内存去指向,而这里没有
?fwd(Widget::MinVals);   // 错误,应该无法链接
  1. 重载的函数名字和模板名字

对于函数名称作为参数时,其实就是个函数指针,并不带参数类型等函数签名,这样完美转发就无从判断具体是哪种重载函数的指针。

  1. 位域,参考以下代码

?struct IPv4Header {
? ?std::uint32_t version:4,
? ? ? ? ? ? ? ? ?IHL:4,
? ? ? ? ? ? ? ? ?DSCP:6,
? ? ? ? ? ? ? ? ?ECN:2,
? ? ? ? ? ? ? ? ?totalLength:16;
?};
??
?void f(sd::size_t sz);
??
?IPv4Header h;
?...
?f(h.totalLength);   // 没问题
?// 引用无法绑定到位域
?fwd(h.totalLength); // 错误
??
?//修改成以下代码,复制位域值就可以
?auto length = static_cast<std::uint16_t>(h.totalLength);
?fwd(length);    // 转发该副本
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 07:31:54  更:2021-07-28 07:33:35 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/25 15:23:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计