C++11:右值引用

目录

一、前言

二、右值引用的使用

1.左值引用解决了什么问题

2.编译器的优化

3.场景1

​编辑 4.场景2

5.不止一个场景

6.int&& r = 10


一、前言

        左值和右值是在C语言中就已经出现的概念,区分左值和右值的最主要的依据就是能不能取地址,即是否有实际的内存空间

        比如

int* p = new int(0);
int b = 1;
const int c = 2;

        我们使用C++语法的规则给变量开辟了空间,那么这些值称为左值。而

int x = 1, y = 2;
//以下都是右值
10;
x + y;
x % y;

        这些值没有被开辟空间,无法取地址,仅仅是一个值或者是一个表达式,称为右值。


        左值引用是在C++的第一个标准C++98中设计的语法,如

// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

        右值引用是在C++11中设计的新语法,它的写法是下面这样的:

int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = x % y;

        我们在C++98中就知道,不能引用常量,现在来看,这句话应该是说,左值引用不能引用右值,比如下面这行代码是编译不通过的

int& r1 = 10;

        但是也有一些特殊之处,const左值引用可以给右值取别名比如下面这行代码又是可以编译通过的

const int& r = 10;

        下面介绍头文件<utility>中的一个函数move

        这个函数一般用来将左值转换为右值,它的返回值是右值。因此,又有了一个特殊之处,右值引用可以给 move(左值)取别名,下面这行代码就可以编译通过

int x = 1;
int&& r = move(x);

二、右值引用的使用

1.左值引用解决了什么问题

        在介绍右值引用的使用场景之前,先来谈谈引用的存在意义,我们写引用并不是像我在介绍概念时那样去单纯的引用变量,而是在写类的成员函数的时候,比如vector的拷贝构造函数的参数就是引用,通过取别名的方式而代替C语言的指针行为,本质的目的是为了减少不必要的拷贝负载,提高程序运行的效率。   

        左值引用如此,C++11新设计的右值引用也是同样的目的。


        那么,禁不住要问,左值引用解决了什么问题

        左值引用解决的第一个问题是C语言使用指针传参的问题,C语言中,函数如果要修改变量,就应该传参为指针,如果要修改指针,就应该传参二级指针,以此类推,这是一项繁琐低效的任务,因此,C++98通过取别名的方式高效解决了这个问题。而如果没有修改原来变量的打算,就是我们常常提到的传值传参,这个过程会发生拷贝复制,如果传参类型是vector,list等这样的对象,会产生巨大的消耗(深拷贝),而引用也解决了这样的问题。

void Func(const string& s);

        第二个问题就是函数返回时所产生的部分问题,在一个函数栈帧销毁后,函数的返回值如果是当前作用域之外的变量,并没有被销毁,我们就可以用引用作返回值。过去,返回值是普通类型时,会产生拷贝复制。而如果用引用作返回类型,可以直接返回,消耗很小。


        那什么情况是左值引用没有解决的呢?

string& func()
{
    string s;
    //·····
    return s;
}
    

        当我们想返回的值是当前函数的局部变量时,函数栈帧销毁后,局部变量也会被销毁,左值引用是无法给这个值取别名的,只能传值返回,这个过程存在拷贝复制的消耗。

        因此,C++11设计新的语法规则右值引用来减少这部分的拷贝复制。

2.编译器的优化

        在介绍右值引用的使用场景之前,来看看右值引用产生之前C++所作的努力。

vector<vector<int>> func()
{
	vector<vector<int>> vv;
	//········

	return vv;
}
vector<vector<int>> ret = func();

        假设存在这样一个场景,func函数的返回值类型如代码所示,此处传值返回存在居多拷贝复制,是消耗巨大的深拷贝。在返回时,先是vv去拷贝构造一个临时变量,再由临时变量拷贝构造ret。在C++编译器做了许多优化后,可以直接用vv去拷贝构造ret,但这仍然是不小的消耗。


        而使用右值引用就可以进一步减小消耗,几乎没有传值返回的拷贝复制了。 但代码并不是像下面这样实现的。

vector<vector<int>>&& func()
{
	vector<vector<int>> vv;
	//········

	return move(vv);
}

        虽然可以编译通过,但是右值引用这样使用未免有点“使劲没使到位的感觉”。

3.场景1

        紧接上面的话题,正确的写法是在string类中新增一个构造函数,称为移动构造函数

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

        这个构造函数不同于拷贝构造函数实现的深拷贝,它只是转移资源,将右值的资源粘贴到我们需要的地方。

        这样一来,上面的代码实际运行情况是:

        关于什么时候是拷贝构造,什么时候是移动构造,就要介绍一下C++中对左值和右值的细节分类理解:

        左值的种类和概念上大差不差,如变量,指针。但是对右值有了更细节的分类:

        1.纯右值(内置类型的右值):10,a+b等

        2.将亡值(即将销毁的值,一般是自定义类型的右值):匿名对象,函数返回表达式等

        那么,在这个例子中,编译器不优化时,vv是左值,调用拷贝构造出临时变量,而临时变量是将亡值,调用移动构造出ret。而编译器优化后,vv直接调用移动构造出ret,虽然vv也是即将销毁的值,但它本质还是左值,因此,在这里,编译器做了隐式的move,将vv转化位将亡值。

      

        STL库在C++11中新增了移动构造函数

 4.场景2

        总结场景1,没有涉及浅拷贝的自定义类不需要移动构造函数,只有深拷贝的类才需要,因为设计移动构造就是为了转移资源,避免深拷贝。

        在上面的场景基础上,如果写这样一行代码 

vector<vector<int>> ret;
ret = func();

        此时程序运行时,所发生的:

        而普通的赋值函数内部还是深拷贝,因此需要将赋值函数也新增右值引用版本。 此时的程序实际发生情况是:

        

5.不止一个场景

       比如 push_back

int main()
{
	list<string> lt;
	string s("1111");

	lt.push_back(s); //调用string的拷贝构造

	lt.push_back("2222");             //调用string的移动构造
	lt.push_back(string("3333")); //调用string的移动构造
	lt.push_back(std::move(s));       //调用string的移动构造
	return 0;
}

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

        诸如此类的insert等等,C++11右值引用出现后,STL库中的许多容器都支持了右值引用,大大提高效率。 

6.int&& r = 10

        这行代码中,10是右值,但是,C++将r的属性设置为左值。

        C++将右值引用的属性设计为左值,是因为设计右值引用是为了转移资源,而右值无法改变,导致swap是错误的写法,而右值引用是左值后,转移资源才是可以正常进行的。

  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值