C++11尝鲜:右值引用和转发型引用

2023-11-15

2013-10-05 19:52  5979人阅读  评论(4)  收藏  举报
  分类:

版权声明:本文为博主原创文章,未经博主允许不得转载。

目录(?)[+]

 

右值引用

为了解决移动语义及完美转发问题,C++11标准引入了右值引用(rvalue reference)这一重要的新概念。右值引用采用T&&这一语法形式,比传统的引用T&(如今被称作左值引用 lvalue reference)多一个&。
如果把经由T&&这一语法形式所产生的引用类型都叫做右值引用,那么这种广义的右值引用又可分为以下三种类型:
  • 无名右值引用
  • 具名右值引用
  • 转发型引用
无名右值引用和具名右值引用的引入主要是为了解决移动语义问题。
转发型引用的引入主要是为了解决完美转发问题。
 

无名右值引用

无名右值引用(unnamed rvalue reference)是指由右值引用相关操作所产生的引用类型。
无名右值引用主要通过返回右值引用的类型转换操作产生, 其语法形式如下:
static_cast<T&&>(t)
标准规定该语法形式将把表达式 t 转换为T类型的无名右值引用。
无名右值引用是右值,标准规定无名右值引用和传统的右值一样具有潜在的可移动性,即它所占有的资源可以被移动(窃取)。
 

std::move()

由于无名右值引用是右值,借助于类型转换操作产生无名右值引用这一手段,左值表达式就可以被转换成右值表达式。为了便于利用这一重要的转换操作,标准库为我们提供了封装这一操作的函数,这就是std::move()。
假设左值表达式 t 的类型为T&,利用以下函数调用就可以把左值表达式 t 转换为T类型的无名右值引用(右值,类型为T&&)。
std::move(t)
 

具名右值引用

如果某个变量或参数被声明为T&&类型,并且T无需推导即可确定,那么这个变量或参数就是一个具名右值引用(named rvalue reference)。
具名右值引用是左值,因为具名右值引用有名字,和传统的左值引用一样可以用操作符&取地址。
与广义的右值引用相对应,狭义的右值引用仅限指具名右值引用。
传统的左值引用可以绑定左值,在某些情况下也可绑定右值。与此不同的是,右值引用只能绑定右值。
右值引用和左值引用统称为引用(reference),它们具有引用的共性,比如都必须在初始化时绑定值,都是左值等等。
[cpp]  view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. struct X {};  
  2. X a;  
  3. X&& b = static_cast<X&&>(a);  
  4. X&& c = std::move(a);  
  5. //static_cast<X&&>(a) 和 std::move(a) 是无名右值引用,是右值  
  6. //b 和 c 是具名右值引用,是左值  
  7. X& d = a;  
  8. X& e = b;  
  9. const X& f = c;  
  10. const X& g = X();  
  11. X&& h = X();  
  12. //左值引用d和e只能绑定左值(包括传统左值:变量a以及新型左值:右值引用b)  
  13. //const左值引用f和g可以绑定左值(右值引用c),也可以绑定右值(临时对象X())  
  14. //右值引用b,c和h只能绑定右值(包括新型右值:无名右值引用std::move(a)以及传统右值:临时对象X())  
 

左右值重载策略

有时我们需要在函数中区分参数的左右值属性,根据参数左右值属性的不同做出不同的处理。适当地采用左右值重载策略,借助于左右值引用参数不同的绑定特性,我们可以利用函数重载来做到这一点。常见的左右值重载策略如下:
[cpp]  view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. struct X {};  
  2. //左值版本  
  3. void f(const X& param1){/*处理左值参数param1*/}  
  4. //右值版本  
  5. void f(X&& param2){/*处理右值参数param2*/}  
  6.   
  7. X a;  
  8. f(a);            //调用左值版本  
  9. f(X());          //调用右值版本  
  10. f(std::move(a)); //调用右值版本  
即在函数重载中分别重载const左值引用和右值引用。
重载const左值引用的为左值版本,这是因为const左值引用参数能绑定左值,而右值引用参数不能绑定左值。
重载右值引用的为右值版本,这是因为虽然const左值引用参数和右值引用参数都能绑定右值,但标准规定右值引用参数的绑定优先度要高于const左值引用参数。
 

移动构造器和移动赋值运算符

在类的构造器和赋值运算符中运用上述左右值重载策略,就会产生两个新的特殊成员函数:移动构造器(move constructor)和移动赋值运算符(move assignment operator)。
[cpp]  view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. struct X  
  2. {  
  3.     X();                         //缺省构造器  
  4.     X(const X& that);            //拷贝构造器  
  5.     X(X&& that);                 //移动构造器  
  6.     X& operator=(const X& that); //拷贝赋值运算符  
  7.     X& operator=(X&& that);      //移动赋值运算符  
  8. };  
  9.   
  10. X a;                             //调用缺省构造器  
  11. X b = a;                         //调用拷贝构造器  
  12. X c = std::move(b);              //调用移动构造器  
  13. b = a;                           //调用拷贝赋值运算符  
  14. c = std::move(b);                //调用移动赋值运算符  
 

移动语义

无名右值引用和具名右值引用的引入主要是为了解决移动语义问题。
移动语义问题是指在某些特定情况下(比如用右值来赋值或构造对象时)如何采用廉价的移动语义替换昂贵的拷贝语义的问题。
移动语义(move semantics)是指某个对象接管另一个对象所拥有的外部资源的所有权。移动语义需要通过移动(窃取)其他对象所拥有的资源来完成。移动语义的具体实现(即一次that对象到this对象的移动(move))通常包含以下若干步骤:
  • 如果this对象自身也拥有资源,释放该资源
  • 将this对象的指针或句柄指向that对象所拥有的资源
  • 将that对象原本指向该资源的指针或句柄设为空值
上述步骤可简单概括为①释放this(this非空时)②移动that
移动语义通常在移动构造器和移动赋值运算符中得以具体实现。两者的区别在于移动构造对象时this对象为空因而①释放this无须进行。

与移动语义相对,传统的拷贝语义(copy semantics)是指某个对象拷贝(复制)另一个对象所拥有的外部资源并获得新生资源的所有权。拷贝语义的具体实现(即一次that对象到this对象的拷贝(copy))通常包含以下若干步骤:
  • 如果this对象自身也拥有资源,释放该资源
  • 拷贝(复制)that对象所拥有的资源
  • 将this对象的指针或句柄指向新生的资源
  • 如果that对象为临时对象(右值),那么拷贝完成之后that对象所拥有的资源将会因that对象被销毁而即刻得以释放
上述步骤可简单概括为①释放this(this非空时)②拷贝that③释放that(that为右值时)
拷贝语义通常在拷贝构造器和拷贝赋值运算符中得以具体实现。两者的区别在于拷贝构造对象时this对象为空因而①释放this无须进行。

比较移动语义与拷贝语义的具体步骤可知,在赋值或构造对象时,
  • 如果源对象that为左值,由于两者效果不同(移动that ≠ 拷贝that),此时移动语义不能用来替换拷贝语义。
  • 如果源对象that为右值,由于两者效果相同(移动that = 拷贝that + 释放that),此时廉价的移动语义(通过指针操作来移动资源)便可以用来替换昂贵的拷贝语义(生成,拷贝然后释放资源)。
由此可知,只要在进行相关操作(比如赋值或构造)时,采取适当的左右值重载策略区分源对象的左右值属性,根据其左右值属性分别采用拷贝语义和移动语义,移动语义问题便可以得到解决。

下面用MemoryBlock这个自我管理内存块的类来具体说明移动语义问题。
[cpp]  view plain  copy  print ? 在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2.   
  3. class MemoryBlock  
  4. {  
  5. public:  
  6.   
  7.     // 构造器(初始化资源)  
  8.     explicit MemoryBlock(size_t length)  
  9.         : _length(length)  
  10.         , _data(new int[length])  
  11.     {  
  12.     }  
  13.   
  14.     // 析构器(释放资源)  
  15.     ~MemoryBlock()  
  16.     {  
  17.         if (_data != nullptr)  
  18.         {  
  19.             delete[] _data;  
  20.         }  
  21.     }  
  22.   
  23.     // 拷贝构造器(实现拷贝语义:拷贝that)  
  24.     MemoryBlock(const MemoryBlock& that)  
  25.         // 拷贝that对象所拥有的资源  
  26.         : _length(that._length)  
  27.         , _data(new int[that._length])  
  28.     {  
  29.         std::copy(that._data, that._data + _length, _data);  
  30.     }  
  31.   
  32.     // 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that)  
  33.     MemoryBlock& operator=(const MemoryBlock& that)  
  34.     {  
  35.         if (this != &that)  
  36.         {  
  37.             // 释放自身的资源  
  38.             delete[] _data;  
  39.   
  40.             // 拷贝that对象所拥有的资源  
  41.             _length = that._length;  
  42.             _data = new int[_length];  
  43.             std::copy(that._data, that._data + _length, _data);  
  44.         }  
  45.         return *this;  
  46.     }  
  47.   
  48.     // 移动构造器(实现移动语义:移动that)  
  49.     MemoryBlock(MemoryBlock&& that)  
  50.         // 将自身的资源指针指向that对象所拥有的资源  
  51.         : _length(that._length)  
  52.         , _data(that._data)  
  53.     {  
  54.         // 将that对象原本指向该资源的指针设为空值  
  55.         that._data = nullptr;  
  56.         that._length = 0;  
  57.     }  
  58.   
  59.     // 移动赋值运算符(实现移动语义:释放this + 移动that)  
  60.     MemoryBlock& operator=(MemoryBlock&& that)  
  61.     {  
  62.         if (this != &that)  
  63.         {  
  64.             // 释放自身的资源  
  65.             delete[] _data;  
  66.   
  67.             // 将自身的资源指针指向that对象所拥有的资源  
  68.             _data = that._data;  
  69.             _length = that._length;  
  70.   
  71.             // 将that对象原本指向该资源的指针设为空值  
  72.             that._data = nullptr;  
  73.             that._length = 0;  
  74.         }  
  75.         return *this;  
  76.     }  
  77. private:  
  78.     size_t _length; // 资源的长度  
  79.     int* _data; // 指向资源的指针,代表资源本身  
  80. };  
  81.   
  82. MemoryBlock f() { return MemoryBlock(50); }  
  83.   
  84. int main()  
  85. {  
  86.     MemoryBlock a = f();            // 调用移动构造器,移动语义  
  87.     MemoryBlock b = a;              // 调用拷贝构造器,拷贝语义  
  88.     MemoryBlock c = std::move(a);   // 调用移动构造器,移动语义  
  89.     a = f();                        // 调用移动赋值运算符,移动语义  
  90.     b = a;                          // 调用拷贝赋值运算符,拷贝语义  
  91.     c = std::move(a);               // 调用移动赋值运算符,移动语义  
  92. }  
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11尝鲜:右值引用和转发型引用 的相关文章

  • MOV指令在32位汇编程序和64位汇编程序下的相同与不同之处

    mov指令原则 两个操作数 目标操作数和源操作数 的大小必须相同 两个操作数不能同时为内存操作数 也就是不能内存 到 内存 指令指针寄存器不能作为目标操作数 64位汇编程序下 32位汇编程序和64位汇编程序都依照上面的规则 语法也相同 但如
  • Linux常用操作命令

    1 ls 列出当前目录中的文件 2 pwd 列出当前目录的绝对路径 3 cd 切换当前目录 4 touch 创建空文件 5 cat 读取文件 6 echo 写文件 7 mkdir 创建目录 文件夹 make directory 8 rm 删
  • VSCode 入门操作大全 + 实用插件推荐【零基础专属详细教程】

    前言 选择一个好的开发工具很重要 很多刚学编程的小伙伴在 webstorm 和 vscode 上很难抉择 我个人更喜欢使用 vscode 因为其有着简洁的操作风格和丰富的人性化的各种功能 这篇文章带给大家 vscode 的新手操作指南 大家
  • Visual Studio配置c环境

    Visual Studio配置c环境 Visual Studio配置c环境 1 下载Visual Studio 下载Visual Studio软件可以直接在其内进行c的运行 不需要配置 官网 其中社区版免费 2 安装Visual Studi
  • tesseract-orc编译及使用(WINDOWS VS 2019)

    1 准备资源 Vs2010或者更高版本 本教程使用vs2019 1 1Tesseract源码 分支切换到3 04 看到vs2010 git地址https github com tesseract ocr tesseract下载源码 文件夹并
  • C语言进阶之路:对任意两个数字求和

    提示 可以参考笔者之前的文章 来对此篇博客进行思考 文章目录 介绍 一 如何正确去书写代码 二 使用步骤 1笔者所写代码 2 重要代码部分 总结 介绍 对本文要记录的大概内容 对任意两个数字进行求加减乘除运算 小数 以下是本篇文章正文内容
  • 01 LNK2038:检测到“RuntimeLibrary”的不匹配项

    LNK2038 检测到 RuntimeLibrary 的不匹配项 问题描述 error LNK2038 检测到 RuntimeLibrary 的不匹配项 解决方法 qtmian lib报错采用方法3解决 方法1 修改VS项目运行库配置 方法
  • 在matlab中编译C++和opencv

    1 在matlab中运行 mex setup命令 选择C 类型 2 运行mex build 此时matlab配置基本完成 3在VS中添加matlab中的库目录和头文件目录 附加库目录 matlab安装目录下面的 extern lib win
  • 在Visual Studio上开启自己的C++学习之旅

    目录 0 引言 1 本教程使用到的相关软件或产品 2 下载及安装Visual Studio 2 1 创建符号链接 2 2 安装Visual Studio 2 2 1 补充 3 创建并运行自己的第一个C 程序 0 引言 在学习一门编程语言之前
  • 用C语言打印九九乘法表

    运用c语言的分支和循环的知识就可以打印出来9 9的乘法表 效果如图 具体代码 可以深刻理解循环和嵌套循环的应用 int main int i 0 行数 for i 1 i lt 9 i 行数 打印9行 int j 0 列数 for j 1
  • 起名字老重名?使用这款利器可以快速帮你查询有哪些站点用了你的名字!

    作者 弗拉德 来源 弗拉德 公众号 fulade me 不知道有没有小伙伴跟我一样 常常在注册账号的时候输入了昵称往往会反回一个 用户名已存在 然后尝试了好几个昵称之后才能成功 今天介绍的这款工具可以帮助我们迅速的检索各大网站有没有我们自己
  • VS登录问题 无法刷新账户凭证 (Microsoft Visual Studio)

    1 问题描述 VS登录时 弹出错误 无法刷新此账户的凭证 2 解决办法 打开默认的浏览器 我的电脑上默认浏览器是搜狗 1 打开选项设置 2 打开Internet选项 点击连接 再点击局域网设置 3 勾选自动检测设置 取消其它勾选 4 VS恢
  • VS 2008配置Winpcap环境

    写在前面的话 这篇博客主要是写给小白看的 因为自己也是一个小白 之前从没有接触过网络嗅探器这些东西 如果说基础的话就是学习过计算机网络 对于计算机网络有一点点了解 再就是对于编程语言基础语法还算熟悉吧 这学期选修了网络攻击与防范这门课程 老
  • OD机试题目【计算网络信号】

    网络信号经过传递会逐层衰减 且遇到阻隔物无法直接穿透 在此情况下需要计算某个位置的网络信号值 注意 网络信号可以绕过阻隔物 array m n 的二维数组代表网格地图 array i j 0代表i行j列是空旷位置 array i j x x
  • 关于一个大一学生的俄罗斯方块项目分享C#开发,附源码(一)

    本人为一双非大一计科新生 这是我第一篇文章 能力一般 水平有限 能在各位大佬面前弄斧 不胜荣幸 事情是这样的 我寒假买了一个3ds掌机 玩了里面很多游戏 其中最令我着迷的就是俄罗斯方块 说实话以前也玩过 但不知怎么就上瘾了 沉迷于刷分 什么
  • 【千律】C++基础:打开并下载网页 -- ShellExecuteEx 和 URLDownloadToFile 函数

    include
  • visual studio:是否统一换行符 提示弹窗是否显示

    工具 选项 环境 文档 加载时检查一致的行尾
  • 通讯录系统图形化界面(C++,Qt5.12)(Visual Studio2019,QtCreator)(初学)

    目录 无用的前言 无用的话 无需用看 前言 一 开发工具 二 功能演示以及 源码和安装包 下载 三 功能介绍以及设计思路 四 代码具体实现 项目文件结构 main cpp mainwindow ui mainwindow h mainwin
  • openCV在Visual Studio2019下的集成使用

    文章目录 下载OpenCV工具 选择合适库文件 使用visual studio创建空项目 测试运行 运行结果 下载OpenCV工具 官网下载实在太慢 还老实下不下来 下面从网上找到些别人分享的一些版本 从3 4到4 7 放到了网盘里 请按需
  • 界面控件DevExpress WPF Dock组件,轻松创建类Visual Studio窗口界面!

    本文主要为大家介绍 DevExpress WPF 控件中的Dock组件 它能帮助用户轻松创还能受Microsoft Visual Studio启发的Dock窗口界面 P S DevExpress WPF拥有120 个控件和库 将帮助您交付满

随机推荐

  • 2019/5/13 基于模型的强化学习方法

    注 论文写作四项工作 工作一 查阅100篇 挑选30篇 核心参考3 5篇 看懂 一篇 工作二 提出难点问题 提出新概念 例 多光谱 注意力机制 工作三 修改算法 网络结构 损失函数 步数 工作四 写写写 改改改 图片精修 丰富实验 首句中心
  • ARM64撬开逆向大门

    图片
  • QML和QWidget混合开发(初探)

    为什么要搞混合开发 Qml已经越来越成为Qt开发的主流 相比与QWidget的界面开发更快 也更容易上手 实现效果上也更好 但老旧项目都是QWidget的框架 大家不可能一次性的把QWidget项目界面全部换成qml 这时候我们可以将新开发
  • python条件运算符_Python中的条件运算符

    python条件运算符 如果条件运算符 if else conditional operator Just like other programming languages Python also provides the feature
  • Spring全家桶

    Spring Spring的架构体系 spring是一个基于java语言写的一个轻量级的一站式解决方案框架 它的最底层是核心容器 在核心容器上面提供了AOP这些中间层技术 然后再往上就可以去集成别人的技术 比如像Dao层的MyBatis J
  • 银河麒麟V10 wireshark安装说明(断网离线)

    下载离线安装包 链接 https pan baidu com s 11QFRmCGlIJrJaiKcHh9Hag pwd u9wv 提取码 u9wv 安装步骤 tar zxvf wireshark tar gz cd wireshark s
  • python连接wss走自己的代理

    我开了一个vpn 然后用py写wss连接 怎么才能让他这个连接走我系统代理呢 vpn 开9090端口 set https proxy socks5 127 0 0 1 9090 set http proxy socks5 127 0 0 1
  • 类的六大默认构造函数

    缺省的构造函数和析构函数 等于放弃了自己初始化和清除的机会 缺省的拷贝构造和缺省的赋值函数 采用 位拷贝和值拷贝 若类中出现指针时 这两个函数出错 class String public String const char str NULL
  • 整理Rapid object detection using a boosted cascade of simple features论文中的要点

    整理Rapid object detection using a boosted cascade of simple features论文中的要点 使用haar特征 在24 24像素的框内有180000以上不同的haar特征 怎么算的 终于
  • openwrt路由器-timeout while waiting for PADS.

    最近使用openwrt路由器进行PPPoE拨号的时候 经常出现 远程服务器无响应 的错误 log打印日志如下 pppoe Timeout waiting for PADS packets Unable to complete PPPoE D
  • c语言错误不允许使用不完整的类型,C语言中的void和void*的定义及用法

    void void最常见的用法 就是在函数中限定函数的参数和返回值的 void draw void 表明函数draw没有参数也没有返回值 void在别的的地方的应用我也没见过 实际上 如果把void 和int char double等类型放
  • 「猜题第一篇」2019年大学生电子设计竞赛

    点击上方 大鱼机器人 选择 置顶 星标公众号 福利干货 第一时间送达 昨天出了清单之后 第一时间我是懵逼的 脑子里想的是 这都是啥啊 后面仔细的理了一下 关于三脚架和小车的用处 极大概率三脚架会是和无人机使用 但也不排除 和小车一起使用然后
  • MYSQL8-快速生成表结构(用于生成文档)

    MYSQL8 快速生成表结构 用于生成文档 SELECT rownum rownum 1 AS 序号 column name AS 代码 CASE WHEN column comment IS NULL OR TRIM column com
  • C语言技巧 ----------调试----------程序员必备技能

    作者前言 作者介绍 作者id 老秦包你会 简单介绍 喜欢学习C语言和python等编程语言 是一位爱分享的博主 有兴趣的小可爱可以来互讨 个人主页 小小页面 gitee页面 秦大大
  • 解决端口被占用问题,安装MySQL出现端口被占用

    1 快捷键 Win R 打开命令提示符 输出命令 netstat ano 目的 查看占用3306端口的 PID 值 上图可以看出 占用3306 窗口的 PID值为 13620 2 打开任务管理器 点击 详细信息 选中该程序 鼠标右键 点击
  • 密度聚类DBSCAN、主成分分析PCA算法讲解及实战(附源码)

    需要源码请点赞关注收藏后评论区留言私信 一 基于密度的聚类 基于密度的聚类算法的主要思想是 只要邻近区域的密度 对象或数据点的数目 超过某个阀值 就把它加到与之相近的聚类中 也就是说 对给定类中的每个数据点 在一个给定范围的区域中必须至少包
  • 操作系统最全面试题汇总

    1 操作系统的特点 共享 资源可被多个并发执行的进程使用 并发 可以在同一时间间隔处理多个进程 需要硬件支持 异步 进程走走停停 每次执行的速度不一样 但是要保证进程每次执行结果相同 虚拟 将物理实体映射成为多个虚拟设备 操作系统的组成 驱
  • Python中的pandas库简介及其使用

    pandas模块 pandas是一个强大的分析结构化数据的工具集 它的使用基础是Numpy 提供高性能的矩阵运算 用于数据挖掘和数据分析 同时也提供数据清洗功能 Pandas中常见的数据结构有两种 Series DateFrame 类似一维
  • 股票相关信息

    股票开头数字代表什么 600 开头的股票是上证 A股 属于大盘股 其中 6006 开头的股票是 最早上市的股票 6016开头的股票为大盘蓝筹股 900 开头的股票是上证 B股 000 开头的股票是深证 A股 001 002开头的股票也都属于
  • C++11尝鲜:右值引用和转发型引用

    C 11尝鲜 右值引用和转发型引用 2013 10 05 19 52 5979人阅读 评论 4 收藏 举报 分类 C 33 版权声明 本文为博主原创文章 未经博主允许不得转载 目录 右值引用 为了解决移动语义及完美转发问题 C 11标准引入