C++11新特性 右值引用

发布于:2022-12-25 ⋅ 阅读:(232) ⋅ 点赞:(0)


左值引用和右值引用的区别?右值引用的意义?

区别:

按照名字:左值引用就是对左值的引用,右值就是对右值的引用
按照功能
左值引用是为了防止对象的拷贝,

  • 函数传参

  • 函数返回值(拷贝函数)

右值引用(C++11):

  • 实现移动语义
  • 实现完美转发

左右值

可以在等号的左边可以取地址为左值,只能在等号右边,不能取地址为右值

左值:

左值是一个数据的表达式(如变量名或引用的指针),我们可以获取到它的地址,正常情况下是可以能够对它赋值,定义const修饰后的左值,不能给它赋值,但是可以取出它的地址。
常见的左值类型如下:

  • 变量名
  • 返回左值的函数调用
  • 前置自增/自减
  • 赋值运算或者符合赋值运算
  • 解引用
int a = 1//a为左值,a具有地址&a
int i = 0++i = 10//a为左值,因为i具有地址
A *a = new a; //*a,解引用依然是左值

右值:

右值也是一个数据表达式,右值是字面常量或者是求值过程种创建的临时对象,右值的生命周期是短暂的,右值不能出现在赋值符号的左边,右值也不能取出地址,更不能对它赋值。

右值的引用语法:

int&& rra = x +y; //正确
rra = 20;         //正确,普通的右值可以被更改
int&& rrb = x;   //错误,右值应用不能引用左值
const int&& rrc = 10;
rrc = 20          //错误,const引用不能更改
int&& rrd = rra; //错误,右值的引用不能被别的右值引用

标准库中的move函数可以将一个左值强制转换为右值

纯右值

  • 字面值
  • 返回非引用类型的函数调用
  • 后置自增自减
  • 算数表达式
  • 逻辑表达式
  • 比较表达式

纯右值的概念等同于我们之前所理解的右值,指的是临时变量或字面量值;而将亡值是C++11新引入的概念,它依托于右值。

  • 将亡值

i++的原理:可以看出因为j是临时对象所以是纯右值。

int j = 1
i = i+1
return i

将亡值

与右值引用的(移动语义)相关的值的类型。在C++中,使用左值去初始化对象或者为对象赋值时候,会调用拷贝构造函数或者赋值构造函数。而使用右值去初始化或者赋值时,会调用移动构造函数 或者移动赋值函数 来移动资源,从而避免拷贝,提高效率。将亡值可以理解为通过移动构造其他变量内存空间的方式获得的值。在确保其他变量不再被使用、或者即将被销毁时,来延长变量值的生命期。

  • 返回右值引用的函数的调用表达式。
  • 转换为右值引用的转换函数的调用表达式。

看代码:
1、先定义Time类,在类中定义了拷贝构造函数和移动构造函数;

#include<algorithm>
#include<iostream>
#include<vector>
using namespace std;
class Time
{
	int *hour;
	int *minute;
	int *second;

    //构造函数
	Time(int h, int m, int s)
	{
		hour = new int(h);
		minute = new int(m);
		second = new int(s);
	}

	//拷贝构造函数
	Time(Time &t)
	{
		cout << "拷贝构造函数" << endl;
		hour = new int(*t.hour);
		minute = new int(*t.minute);
		second = new int(*t.second);
	}

	//移动构造函数
	Time(Time &&t) noexcept :hour(t.hour), minute(t.minute), second(t.second)
	{
		t.hour = nullptr;
		t.minute = nullptr;
		t.second = nullptr;
		std::cout << "move contruct." << std::endl;

	}


	~Time()
	{
		std::cout << "call ~Time()" << std::endl;
		delete hour;
		delete minute;
		delete second;
	}
};
int main()
{
    Time test(10,25,12);
    Time test2(test);
    return 0;
}

执行结果:

copy contruct.
call ~Time()
call ~Time()

通过这样的方式来减少不必要的内存操作。但是之后我们也无法再访问 test 对象的内容了,因为都在移动构造函数之中置为了空指针。将亡值通过移动构造函数“借尸还魂”,通过 test2 变量延续了自己的生命周期。

https://blog.csdn.net/itworld123/article/details/115470227

移动拷贝构造函数

有必要在这解释一下 移动拷贝构造函数
左值引用的缺陷:如果一个函数的返回值出了该函数作用域后就会被销毁掉,这个返回值的生命周期就结束了,因此就不能使用传引用返回,所以只能通过传值返回,而传值返回至少需要进行一次拷贝构造(如果编译器比较旧,就需要进行两次的拷贝构造),在旧的编译器下,如果一个函数是返回值给另一个函数并创建出新对象的时候,中间过程中需要先拷贝构造出一个临时对象,然后再通过这个临时对象在另一个函数内拷贝构造出一个新对象,然后这个临时对象就会被销毁掉。但是有的编译器为了提高拷贝效率,就将调用两次拷贝构造函数优化只调用一次拷贝构造函数
在这里插入图片描述

原文链接:https://blog.csdn.net/sjp11/article/details/122830135

移动拷贝构造函数跟构造函数一样,参数需要是一个本身类型的对象,但移动拷贝构造函数的参数是一个该类型的右值引用。移动拷贝函数创建出一个新对象,都将新对象中的值都设置为0,接下来与传进来的右值对象进行资源交换。

//移动构造

string(string&& s):_str(nullptr),_size(0),_capacity(0)
{
    cout << '看看构造'<<endl;
    swap(s);
}
int main()
{
    sjp::string s1('詹姆斯是条狗');
    sjp::string s2(s1);
    sjp::string s3(std::move(s1));
}

使用移动拷贝构造函数后,源对象指向资源就被交换出去,这些资源的所有权都归属到了新对象,因此,如果源对象是一个长期存在的对象的时候,需要谨慎使用移动拷贝构造函数。调用移动拷贝构造函数创建出s3,s1的资源被转移到了s3,s1中没有指向任何资源,所以就不能通过s1去寻找之前的资源。
当函数的返回值进行返回的时候,它会调用拷贝出一个 临时对象,此时返回值就会去拷贝出一个临时对象,由于返回值是一个将亡值,属于一个右值,因此就会调用移动拷贝构造函数拷贝出临时对象,然后临时对象去拷贝出新对象,由于临时对象也属于一个右值,所以回去调用移动拷贝构造函数去拷贝出新对象。由于两个过程都是调用移动拷贝构造函数,则编译器会直接优化成只调用一次移动拷贝构造函数。
如下,str对象中包含一个指针指向堆上一个字符串,当我们调用移动拷贝构造函数创建出s的时候,可以将str对象中的资源和s的对象资源进行交换,所以指向字符串的指针就给了s,因此s对象就可以通过指针使用该字符串,这就不需要去堆上再创建一块新的字符串给s对象。


网站公告

今日签到

点亮在社区的每一天
去签到