C++11【右值引用,移动语义,完美转发】

文章目录

    • 左值引用和右值引用
    • 左值引用与右值引用比较
    • 右值引用使用场景和意义
      • 左值引用的使用场景
      • 左值引用的短板
      • 右值引用和移动语义
      • 右值引用引用左值及其一些更深入的使用场景分析
    • 完美转发
      • 万能引用
      • std::forward 完美转发在传参的过程中保留对象原生类型属性
      • 完美转发实际中的使用场景:

左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名。

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针)我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址左值引用就是给左值的引用,给左值取别名。
左值引用就是对左值的引用,给左值取别名,通过“&”来声明。

int main()
{// 以下的p、b、c、*p都是左值int* p = new int(0);int b = 1;const int c = 2;// 以下几个是对上面左值的左值引用int*& rp = p;int& rb = b;const int& rc = c;int& pvalue = *p;return 0;
}

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。****右值引用就是对右值的引用,给右值取别名。****
右值引用就是对右值的引用,给右值取别名,通过“&&”来声明。

int main()
{double x = 1.1, y = 2.2;// 以下几个都是常见的右值10;x + y;fmin(x, y);// 以下几个都是对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y);// 这里编译会报错:error C2106: “=”: 左操作数必须为左值10 = 1;x + y = 1;fmin(x, y) = 1;return 0;
}

1.右值的本质就是一个临时变量或者常量值。
2. 右值不能被取地址是因为临时变量或者常量值没有被存储起来,只有被存储起来才有地址
3. 函数返回值指的是传值返回,因为船只返回返回的是拷贝,是一个临时变量。

注意:右值是不能被取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址。
例如:不能取字面量10的地址,但是用rr1引用后,可以对rr1取地址,并且可以修改rr1,如果不想被修改,可以用
const int&& rr1引用。

左值引用与右值引用比较

左值引用总结:
1.左值引用只能引用左值,不能引用右值。
2.但是const左值引用可以引用左值,也可以引用右值。

int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a;   // ra为a的别名//int& ra2 = 10;   // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

右值引用总结:
1.右值引用只能引用右值,不能引用左值。
2.但是右值引用可以引用move后的左值。

int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

注意:move不会改变当前变量的属性,move的返回值是右值。
C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转换为右值引用,然后实现移动语义。

右值引用使用场景和意义

对于左值引用既可以引用左值也可以引用右值,C++11还要提出了右值引用,实际上左值引用也存在短板,右值引用是用来补齐短板的。

为了更好的说明问题,模拟实现一个string:

namespace nzq
{class string{public:typedef char* iterator;iterator begin(){return _str; }iterator end(){return _str + _size; }//构造函数string(const char* str = ""){_size = strlen(str); _capacity = _size; _str = new char[_capacity + 1]; strcpy(_str, str); }//交换两个对象的数据void swap(string& s){std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity);}//拷贝构造函数string(const string& s):_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 深拷贝" << endl;string tmp(s._str); swap(tmp); }//赋值运算符重载string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;string tmp(s); swap(tmp); return *this; }//析构函数~string(){delete[] _str;  _str = nullptr; _size = 0;      _capacity = 0;  }//[]运算符重载char& operator[](size_t i){assert(i < _size);return _str[i];}//改变容量,大小不变void reserve(size_t n){if (n > _capacity) {char* tmp = new char[n + 1]; strncpy(tmp, _str, _size + 1); delete[] _str; _str = tmp; _capacity = n; }}//尾插字符void push_back(char ch){if (_size == _capacity) {reserve(_capacity == 0 ? 4 : _capacity * 2); }_str[_size] = ch; _str[_size + 1] = '\0'; _size++; }//+=运算符重载string& operator+=(char ch){push_back(ch); return *this; }const char* c_str()const{return _str;}private:char* _str;size_t _size;size_t _capacity;};
}

左值引用的使用场景

做参数和做返回值都可以提高效率。

void func1(nzq::string s)
{}
void func2(const nzq::string& s)
{}
int main()
{nzq::string s1("hello world");// func1和func2的调用我们可以看到左值引用做参数减少了拷贝,提高效率的使用场景和价值func1(s1);func2(s1);// string operator+=(char ch) 传值返回存在深拷贝// string& operator+=(char ch) 传左值引用没有拷贝提高了效率s1 += '!';return 0;
}

在这里插入图片描述

我们可以看到左值引用确实提高了效率,由于fun2的形参是左值引用,避免了一次深拷贝。

左值引用的短板

当函数返回对象是一个局部变量,出了作用域就不存在了,就不能左值引用返回,只能传值返回。
例如nzq::string to_string(int value)函数中看到,这里只能传值返回,传值返回会导致至少一次拷贝构造(旧的编译器可能是两次)

	nzq::string to_string(int x){nzq::string ret;while (x){int val = x % 10;x /= 10;ret += ('0' + val);}reverse(ret.begin(), ret.end());return ret;}
int main()
{nzq::string s2 = nzq::to_string(1324);return 0;
}

在这里插入图片描述
在这里插入图片描述

C++11提出右值引用就是为了解决左值引用的这个短板的,但解决方式并不是简单的将右值引用作为函数的返回值。

右值引用和移动语义

解决方法:移动构造和移动赋值

移动构造:

移动构造本质是将参数右值的资源窃取过来,占为己有,那么就用做深拷贝了,所以叫移动构造,就是窃取别人的资源来构造自己。

		// 移动构造string(string&& s){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}

移动构造和拷贝构造的区别:

  1. 未添加移动构造之前,拷贝构造采用的const左值接收参数,无论传入的左值还是右值,都会调用拷贝构造
  2. 添加后,移动构造采用的右值引用接收参数,根据最匹配原则,传入的值是右值时,就会调用移动构造

注意:右值分类:
3. 纯右值,内置类型右值
4. 将亡值 ,自定义类型右值
eg:to_string的返回值ret就是一个将亡值。将亡值是一种在函数调用结束后会立即被销毁的值,匿名对象也可以叫做将亡值,因为将亡值马上就被销毁了,不如把资源转移给别人用,因此编译器会将其识别成右值。

在这里插入图片描述

移动赋值:

string& operator=(string&& s){cout << "string& operator=(string&& s)-- 移动赋值" << endl;swap(s);return *this;}

移动赋值和原有operator=函数的区别:

  1. 未添加移动赋值之前,原有operator=采用的是const左值,无论传入左值还是右值,都会调用operator=;
  2. 添加后,根据最匹配原则,传入右值,就要调用移动赋值。

STL容器

C++11出来后,STL的容器增加了移动构造和移动赋值

在这里插入图片描述

在这里插入图片描述

右值引用引用左值及其一些更深入的使用场景分析

按照语法,右值引用只能引用右值,但右值引用一定不能引用左值吗?因为:有些场景下,可能真的需要用右值去引用左值实现移动语义。当需要用右值引用引用一个左值时,可以通过move函数将左值转化为右值。
C++11中,std::move()函数位于 头文件中,该函数名字具有迷惑性,它并不搬移任何东西,唯一的功能就是将一个左值强制转化为右值引用,然后实现移动语义。

move定义:

template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movablereturn ((typename remove_reference<_Ty>::type&&)_Arg);
}

STL容器插入接口函数也增加了右值引用版本:

在这里插入图片描述

右值插入的意义:

int main()
{list<nzq::string> lt;nzq::string s("1111");nzq.push_back(s); //调用string的拷贝构造nzq.push_back("2222");             //调用string的移动构造nzq.push_back(cl::string("3333")); //调用string的移动构造nzq.push_back(std::move(s));       //调用string的移动构造return 0;
}

C++11出来之后,string类提供了移动构造函数,并且list容器的push_back接口提供了右值引用版本,此时如果传入push_back函数的string对象是一个右值,那么在push_back函数中构造结点时,这个右值就可以匹配到string的移动构造函数进行资源的转移,这样就避免了深拷贝,提高了效率。

完美转发

万能引用

  1. 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
  2. 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
  3. 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值, 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用完美转发
void Func(int& x)
{cout << "左值引用" << endl;
}
void Func(const int& x)
{cout << "const 左值引用" << endl;
}
void Func(int&& x)
{cout << "右值引用" << endl;
}
void Func(const int&& x)
{cout << "const 右值引用" << endl;
}
template<class T>
void PerfectForward(T&& t)
{Func(t);
}
int main()
{int a = 10;PerfectForward(a);       //左值PerfectForward(move(a)); //右值const int b = 20;PerfectForward(b);       //const 左值PerfectForward(move(b)); //const 右值return 0;
}

在这里插入图片描述
由于PerfectForward函数的参数类型是万能引用,因此既可以接收左值也可以接收右值,而我们在PerfectForward函数中调用Func函数,就是希望调用PerfectForward函数时传入左值、右值、const左值、const右值,能够匹配到对应版本的Func函数。
但是实际上最终都匹配到了左值引用版本的Func函数。
也就是说,右值经过一次参数传递后其属性会退化成左值,如果想要在这个过程中保持右值的属性,就需要用到完美转发。

std::forward 完美转发在传参的过程中保留对象原生类型属性

template<class T>
void PerfectForward(T&& t)
{Func(std::forward<T>(t));
}

在这里插入图片描述
经过完美转发后,调用PerfectForward函数时传入的是右值就会匹配到右值引用版本的Func函数,传入的是const右值就会匹配到const右值引用版本的Func函数,这就是完美转发的价值。

完美转发实际中的使用场景:

对于简化的list

namespace nzq
{template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& x = T()):_data(x),_next(nullptr),_prev(nullptr){}//为了insert里面新节点的创建重载的初始化list_node(T&& x):_data(move(x)), _next(nullptr), _prev(nullptr){}};template<class T>class list{typedef list_node<T> Node;public://左值版本的push_backvoid push_back(const T& x){insert(end(), x);}//右值版本的void push_back(T&& x){insert(end(), forward<T>(x));}//左值版本iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}//右值版本iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newnode = new Node(forward<T>(x));Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;++_size;return iterator(newnode);}private:Node* _head;};
}

分别传入左值和右值调用:

int main()
{nzq::list<nzq::string> lt;nzq::string s1("hello world");lt.push_back(s1);//调用左值版本lt.push_back(nzq::to_string(12334));//调用右值版本lt.push_back("111111");//调用右值版本return 0;
}

在右值版本的push_back 和 insert中用到了完美转发:
push_back: 因为实参是右值,右值被右值引用过后 属性变为了左值,不完美转换就去调用左值的inset了。
insert:右值被右值引用过后 属性变为了左值,然后在new node时完美转发 让属性变回右值 从而去调用上面的右值初始化, 然后经过_data(move(x)), 去调用移动构造。

注意: 代码中push_back和insert函数的参数T&&是右值引用,而不是万能引用,因为在list对象创建时这个类就被实例化了,后续调用push_back和insert函数时,参数T&&中的T已经是一个确定的类型了,而不是在调用push_back和insert函数时才进行类型推导的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/221254.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【从服务器获取共享列表失败】【无法与设备或资源通信】解决方案!

【从服务器获取共享列表失败】背景&#xff1a; 某项目搭建有samba共享&#xff0c;使用一段时间后&#xff0c;不知何种原因&#xff0c;客户端链接共享时报&#xff1a;从服务器获取共享列表失败&#xff0c;无效的参数。 可参考解决方案A&#xff1a; 银河麒麟samba共享文…

Python---IP 地址的介绍

1. IP 地址的概念 IP 地址就是标识网络中设备的一个地址&#xff0c;好比现实生活中的家庭地址。 网络中的设备效果图: 2. IP 地址的表现形式 说明: IP 地址分为两类&#xff1a; IPv4 和 IPv6 IPv4 是目前使用的ip地址 IPv6 是未来使用的ip地址 IPv4 是由点分十进制组成 …

SpringBoot接入轻量级分布式日志框架GrayLog

1.前言 日志在我们日常开发定位错误&#xff0c;链路错误排查时必不可少&#xff0c;如果我们只有一个服务&#xff0c;我们可以只简单的通过打印的日志文件进行排查定位就可以&#xff0c;但是在分布式服务环境下&#xff0c;多个环境的日志统一收集、展示则成为一个问题。目…

【STM32工具篇】使用CLion开发STM32

本文主要记录使用CLion开发STM32&#xff0c;并调试相关功能 使用的CLion版本&#xff1a;2023.3.1 CLion嵌入式配置教程&#xff1a;STM32CubeMX项目 |CLion 文档 (jetbrains.com) OpenOCD官网下载&#xff1a;Download OpenOCD for Windows (gnutoolchains.com) GNU ARM工…

C# 将 Word 转化分享为电子期刊

目录 需求 方案分析 相关库引入 关键代码 Word 转 Pdf Pdf 转批量 Jpeg Jpeg 转为电子书 实现效果演示 小结 需求 曾经的一个项目&#xff0c;要求实现制作电子期刊定期发送给企业进行阅读&#xff0c;基本的需求如下&#xff1a; 1、由编辑人员使用 Microsoft Word…

项目实战:自动驾驶之方向盘操纵

项目介绍 根据汽车前方摄像头捕捉的画面,控制汽车方向盘转动的方向和角度,这是自动驾驶要解决的核心问题。这个项目主要是通过使用深度神经网络解决一个回归问题。不同于分类、识别场景,回归问题中神经网络输出的是一个连续的值。 通过这个项目的学习,可以将神经网络用于通…

Oracle定时任务的创建与禁用/删除

在开始操作之前&#xff0c;先从三W开始&#xff0c;即我常说的what 是什么&#xff1b;why 为什么使用&#xff1b;how 如何使用。 一、Oracle定时器是什么 Oracle定时器是一种用于在特定时间执行任务或存储过程的工具&#xff0c;可以根据需求设置不同的时间段和频率来执行…

PostgresSQL数据库中分区和分表的区别以及PostgresSQL创建表分区分表示例

1.分区分表理解 数据库分区和分表都是数据库中常用的数据分散存储技术&#xff0c;但它们的实现方式和应用场景有所不同。 分表&#xff1a;将一个大的表拆分成多个小的表&#xff0c;每个子表存储一部分数据。分表可以减轻单个表的数据量&#xff0c;提高查询效率&#xff0c…

【计算机四级(网络工程师)笔记】操作系统概论

目录 一、OS的概念 1.1OS的定义 1.2OS的特征 1.2.1并发性 1.2.2共享性 1.2.3随机性 1.3研究OS的观点 1.3.1软件的观点 1.3.2资源管理器的观点 1.3.3进程的观点 1.3.4虚拟机的观点 1.3.5服务提供者的观点 二、OS的分类 2.1批处理操作系统 2.2分时操作系统 2.3实时操作系统 2.4嵌…

0基础学java-day21(网络编程)

一、网络的相关概念 1 网络通信 2 网络 3 ip 地址 4.ipv4 地址分类 5.域名 6 网络通信协议 7.网络通信协议 8.TCP 和 UDP 二、InetAddress 类 &Socket 1 相关方法 package com.hspedu.api;import java.net.InetAddress; import java.net.UnknownHostException;/*** …

Azure Machine Learning - 提示工程高级技术

本指南将指导你提示设计和提示工程方面的一些高级技术。 关注TechLead&#xff0c;分享AI全维度知识。作者拥有10年互联网服务架构、AI产品研发经验、团队管理经验&#xff0c;同济本复旦硕&#xff0c;复旦机器人智能实验室成员&#xff0c;阿里云认证的资深架构师&#xff0c…

分布式链路追踪 —— 基于Dubbo的traceId追踪传递

文章目录 **原文链接&#xff0c;点击跳转**RpcContext 上下文对象Dubbo 过滤器&#xff08;Filter&#xff09;对象基于Dubbo的traceId追踪传递实现 原文链接&#xff0c;点击跳转 RpcContext 上下文对象 在实现 Dubbo 调用之间的链路跟踪之前&#xff0c;先简单了解 RpcCon…

【开源项目】WPF 扩展 -- 多画面视频渲染组件

目录 1、项目介绍 2、组件集成 2.1 下载地址 2.2 添加依赖 3、使用示例 3.1 启动动画 3.2 视频渲染 3.3 效果展示 4、项目地址 1、项目介绍 Com.Gitusme.Net.Extensiones.Wpf 是一款 Wpf 扩展组件。基于.Net Core 3.1 开发&#xff0c;当前是第一个发布版本 1.0.0&am…

node.js mongoose

目录 官方文档 mongoose Schema Model Query document 关系 官方文档 Mongoose v8.0.3: Getting Started mongoose Mongoose 是一个 Node.js 环境下 MongoDB 的对象建模工具。它提供了一种在应用程序中与 MongoDB 数据库进行交互的方式&#xff0c;使得开发者能够使用…

超结MOS/低压MOS在5G基站电源上的应用-REASUNOS瑞森半导体

一、前言 5G基站是5G网络的核心设备&#xff0c;实现有线通信网络与无线终端之间的无线信号传输&#xff0c;5G基站主要分为宏基站和小基站。5G基站由于通信设备功耗大&#xff0c;采用由电源插座、交直流配电、防雷器、整流模块和监控模块组成的电气柜。所以顾名思义&#xf…

石器时代H5小游戏架设教程

本文讲解石器时代 H5 之恐龙宝贝架设教程&#xff0c;想研究 H5 游戏如何实现&#xff0c;那请跟着此次教程学习在拥有小游戏源码的情况下该如何搭建起来 开始架设 1. 架设条件 石器时代架设需要准备&#xff1a; 一台linux 服务器&#xff0c;建议 CentOs 7.6 版本&#xf…

【ranger】CDP环境 更新 ranger 权限策略会发生低概率丢失权限策略的解决方法

一、问题描述&#xff1a; 我们的 kafka 服务在更新&#xff08;添加&#xff09; ranger 权限时&#xff0c;会有极低的概率导致 MM2 同步服务报错&#xff0c;报错内容 Not Authorized。但是查看 ranger 权限是赋予的&#xff0c;并且很早配置的权限策略也会报错。 相关组件…

JDK bug:ciObjectFactory::create_new_metadata:原因完全解析

文章目录 1、问题2.详细日志2.关键日志3.结论4.JDK&#xff1a;bug最终bug链接&#xff1a; 京东遇到过类似bug各位大佬如果有更详细的解答可以留言。 1、问题 服务不通&#xff0c;接口404&#xff0c;查看日志有一下截图&#xff0c;还有一个更详细的日志 2.详细日志 # #…

RPC(3):HttpClient实现RPC之GET请求

1HttpClient简介 在JDK中java.net包下提供了用户HTTP访问的基本功能&#xff0c;但是它缺少灵活性或许多应用所需要的功能。 HttpClient起初是Apache Jakarta Common 的子项目。用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包&#xff0c;并且它支持 H…

文件操作入门指南

目录 一、为什么使用文件 二、什么是文件 2.1 程序文件 2.2 数据文件 2.3 文件名 三、文件的打开和关闭 3.1 文件指针 3.2 文件的打开和关闭 四、文件的顺序读写 ​编辑 &#x1f33b;深入理解 “流”&#xff1a; &#x1f342;文件的顺序读写函数介绍&#xff1a; …