首页 > 其他分享 >右值引用相关整理

右值引用相关整理

时间:2023-12-16 23:45:47浏览次数:31  
标签:右值 int 左值 引用 && 整理 MyClass

前言

 C++中有“移动语义、右值引用、引用折叠、万能引用、完美转发”等许多概念,本文尽量按照时间顺序,理清这些概念的由来,有什么关系,解决了什么问题。

移动语义

 首先介绍移动语义的概念。移动语义是C++11引入的,正如移动一词的含义:一个物体从地点A到地点B,从结果上看,物体位置发生了变化还是一个物体

class MyClass
{
public:
	MyClass();
	~MyClass();

private:
	int* array;
};

 假设我们有如下一个类MyClass定义如下。编译器默认的拷贝构造函数是浅拷贝,array指针会指向一块内存区域如果一种拷贝方式,可以实现浅拷贝。但很多时候,为了避免重复释放、以及完全复制,会手动写拷贝构造,进行深拷贝,这比较耗费资源。某些情况下,耗时是有意义的,比如我们可能希望备份一个完全相同的对象。
 但如果拷贝源是一个临时对象,拷贝完成后,临时对象就会被释放,那这种深拷贝,就没有意义了。比如:
1.存放在vector容器中的MyClass对象,在vector容量满了的时候,会进行扩容申请更大的内存用来复制原来的对象。扩容完成后,原来的内存空间的对象就没有意义了。
2.一些多线程场景,比如packaged_task。语义是封装的任务,不该被多个线程执行。所以该类就禁止拷贝构造,只有移动构造。
 于是,为了区分一般对象和临时对象,在构造和赋值MyClass时,能够正确调用复制和移动,引入了右值和右值引用的概念。在代码中移动构造函数和移动赋值函数声明如下:&&标志着传参是一个临时对象,可以剥夺其内容,也就是右值引用。

MyClass(MyClass&& other);
MyClass operator=(MyClass&& other);

 右值:可以理解为不能取地址的对象。比如函数的返回值,往往是临时对象;具体值1,“aaa”。

int a =1;   //a是左值,1,是右值
std::string str = “ssss”; //str是左值,“ssss”是右值
int b = 4*7;   //这里4*7的中间过程是右值

 右值引用,就是对右值的引用,右值引用由右值进行初始化(和左值引用由左值初始化类似),但是右值引用本身是个左值,也就是可以取地址。

int&& r = 1;  //r为右值引用
int&& b =r;  //错误,r为左值
MyClass t;
MyClass&& m =std::move(t);  //m为右值引用

引用折叠和万能引用

 引用折叠是描述“引用的引用,最后是什么类型的规则”。c++不允许我们显式的写出引用的引用,但某些场景模板、auto推断等,可以构造出如下的情况

template <typename T>
class MyClass {
public:
	typedef T&& ReturnValue1;
	typedef T& ReturnValue2;
	typedef T ReturnValue3;
};

	MyClass<int&>::ReturnValue1 r11 = a;     //&  &&=>&
	MyClass<int>::ReturnValue1 r12 = 0;      //   &&=>&&
	MyClass<int&&>::ReturnValue1 r13 = 0;    //&& &&=>&&

	MyClass<int&>::ReturnValue2 r21 = a;     //&  & =>&
	MyClass<int>::ReturnValue2 r22 = a;      //   & =>&
	MyClass<int&&>::ReturnValue2 r23 = a;    //&& & =>&

	MyClass<int&>::ReturnValue3 r31 = a;     //&    =>&
	MyClass<int>::ReturnValue3 r32 = a;      //     =>
	MyClass<int&&>::ReturnValue3 r32 = 1;    //  && =>&&

 万能引用概念的引入,是由于模板函数既要支持参数为左值引用,又要支持右值引用。万能引用看起来和右值引用相似,但完全不是一个概念。可以参考Universal References in C++11 -- Scott Meyers。大佬对万能引用的定义如下。

The essence of the issue is that “&&” in a type declaration sometimes means rvalue reference, but sometimes it means either rvalue reference or lvalue reference. As such, some occurrences of “&&” in source code may actually have the meaning of “&”, i.e., have the syntactic appearance of an rvalue reference (“&&”), but the meaning of an lvalue reference (“&”). References where this is possible are more flexible than either lvalue references or rvalue references. Rvalue references may bind only to rvalues, for example, and lvalue references, in addition to being able to bind to lvalues, may bind to rvalues only under restricted circumstances.[1] In contrast, references declared with “&&” that may be either lvalue references or rvalue references may bind to anything. Such unusually flexible references deserve their own name. I call them universal references.

 万能引用一般写作如下形式,其表达式告告诉编译器,T如果是左值(int,int&,int&&),则T推到成左值引用int&,反之推到成int&&。再结合引用折叠,就可以知道T的实际类型。

template<typename T>
void func(T&& t)
{
	
}

std::move()

 接着就可以介绍模板函数std::move了,因为它就是借助了万能引用来实现的。std::move的功能就是把左值转化为右值,就完事了。接着让那些以右值引用作为形参的函数,能够调用移动构造(这是接受右值的函数做的事)。std::move的源码如下:拿int举个例子,无论左值(int、int&,int&&)还是右值(临时对象),编译器会将_Arg推到为左值引用(int&)或者右值引用(int&&),然后remove_reference_t<_Ty>的作用是去除引用(int),static_cast强制转化,会转化为右值引用(int&&)。总结起来这个函数做了两点:1.将左值或右值转化为右值引用。2.函数返回右值引用(返回右值引用,被看做返回右值)。所以这个move函数本身其实没有调用移动构造。

template <class _Ty> _NODISCARD constexpr remove_reference_t<_Ty>&& move(_Ty&& _Arg) noexcept 
{ // forward _Arg as movable    
 return static_cast<remove_reference_t<_Ty>&&>(_Arg); 
}

 另外:std::move为什么不直接返回一个传入的对象,这样函数返回值不就是右值吗?的确,但是那move函数在返回的时候,要注意返回类型是否会造成值拷贝?效率和能否编译(如果拷贝被delete)都是问题。

完美转发std::forward()

  template<typename _Tp>
    inline _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) 
    { return static_cast<_Tp&&>(__t); }
 
  template<typename _Tp>
    inline _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) 
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }

 完美转发是万能引用的进阶版,主要处理函数模板中传入参数是右值,但右值引用本身是个左值的问题。就是说函数模板传入的都会变成右值,如果函数模板1中再调用函数模板2,那函数模板1传给函数模板2的参数一定是左值(&或&&)。std::forward的模板如下,注意这不是万能引用,这分别是左值和右值的模板函数。这里也用到引用折叠。无论_Tp是什么类型(左值、或右值),都可以被左值引用或者右值引用匹配(也就是下面任意一个函数)。左值(int,int&,int&&),经过引用折叠,分别是int&&,int&,int&&。虽然有奇怪,但是如果函数内传的是int,forward之后变成了右值int&&,验证如下。forward的正确用法如果只是在万能引用中,也就是模板函数中(T一定会被推到成int&或者int&&),那forward函数还是没问题的。

//验证如下:std::forward<int>(a)被推到成int&&
void func2(int&& num) {

}

	int a = 0;
	func2(std::forward<int>(a));   //编译通过

总结

 补充:现代编译可能对函数返回值会进行优化,不再进行耗时的值拷贝了。要去优化之后才能验证一些东西。
 c++真的太细碎、太复杂了

标签:右值,int,左值,引用,&&,整理,MyClass
From: https://www.cnblogs.com/chendasxian/p/17904676.html

相关文章

  • 小红书整理
    小红书1、旋转数组[经典题目][二分查找]2、快速排序[经典题目][十大排序算法]classSolution:defquickSort(self,arr):defpartition(arr,left,right):low,high=left,rightkey=arr[left]whileleft<right:......
  • C++基础 -7- 引用
    ———————引用———————引用就是数据本身不占用空间 ......
  • 60道KafKa高频题整理(附答案背诵版)
    废话不多说,直接上干货简述什么是Kafka的Topic?Kafka的Topic是一个存储消息的逻辑概念,可以认为是一个消息集合。每条消息发送到Kafka集群的消息都有一个类别,这个类别就是Topic。物理上来说,不同的Topic的消息是分开存储的,每个Topic可以有多个生产者向它发送消息,也可......
  • 未能加载文件或程序集“Newtonsoft.Json”或它的某一个依赖项。找到的程序集清单定义
    原文链接:https://blog.csdn.net/weixin_45488182/article/details/132537085网上的资料,大都是因为版本号不一致,我检查了很多遍,我这边版本号是12.0.1与12.0.2,config里是12.0.0,应该算是一致的吧。并且清理重新生成后,就不会报这个错。程序可以正常运行了。今天终于解决了这个问题,......
  • 引用不同版本dll
    问题描述一个项目引用不同版本的同一dll,会引发以下报错:未能加载文件或程序集“xxx,Version=x.x.x.x,Culture=neutral,PublicKeyToken=xxxxxxxxxxxx”或它的某一个依赖项。系统找不到指定的文件这里来解决项目中同一dll的多版本问题。解决方式通过配置web.config配置文件(app......
  • Selenium系列知识点整理--个人总结
    Selenium系列知识点整理-----https://www.cnblogs.com/yoyoketang/-----本文摘录于‘上海-悠悠’的博客,网址如上  新手学习selenium路线图(老司机亲手绘制)-学前篇  学习selenium主要分六个阶段,自己在哪个层级,可以对号入座下。第一阶段:幼儿园1.选语言:在学习自动......
  • 七、变量的定义和引用
    七、变量的定义和引用7.1变量的引用和调试变量的调试在ansible中有一个debug模块,专门用来打印输出调试信息。它只有两个参数msg和var。msg:打印信息,类似于shell的echo和python的printvar:只能打印变量信息-name:installhttpdhosts:allvars:username:......
  • Go函数参数传递到底是值传递还是引用传递?
    在函数中,如果参数是非引用类型(int、string、array、struct等),这样就在函数中就无法修改原内容数据;如果参数是引用类型(指针、map、slice、chan等),这样就可以修改原内容数据。是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言......
  • 微积分 A1 要点整理
    期中考试前太鸽了就不补了,这里主要是期中考试之后的部分。不定积分不定积分的本质:找原函数。称函数\(F\)为\(f\)的原函数,当且仅当对于\(f\)定义域中的所有\(x\),都有\(F'(x)=f(x)\)。记\(\intf(x)\mathrmdx\)为\(f\)所有原函数的集合,称作\(f\)的不定积分。可......
  • MinGW编译Python至pyd踩坑整理
    注意需要魔法用scoop自动安装配置MinGw需要魔法,不需要手动配置mingwscoopinstallmingw安装Cython,Setuptools第三方库关闭魔法,使用清华源pipinstallsetuptools-ihttps://pypi.tuna.tsinghua.edu.cn/simplepipinstallcython-ihttps://pypi.tuna.tsinghua.edu.cn/s......