右值引用
还是谈一谈我对右值引用的理解
有关右值引用的讲解网上有很多,这里就不在一一罗列了
右值引用,毫无疑问是用来解决冗余拷贝的问题的。
比如在拷贝构造函数里实现了深拷贝,那么在某些情况下我们就有可能重复的进行大量资源的拷贝,造成资源的浪费
C++的编译器有对这方面的优化,叫做RVO(return value optimization)
我们在函数返回的时候,不进行一次次的拷贝构造,而是直接将对象构造在他应该放的地方,从而省略了中途冗余的拷贝
但是这毕竟只是编译器做的优化,不能完全帮助我们优化性能,所以有的时候这些工作还是需要我们自己来做的
于是右值引用在C++11中应运而生,右值引用的作用就是帮我们延长临时变量的生命周期,从而直接从构造的临时变量中获取资源
这里不详细讲解右值引用的具体语法,而是谈几个自己在理解右值引用过程中的易错点
首先,引用,无论是左值引用,还是右值引用,他们都只是一个别名,他们是原始对象的一个别称。
引用自身是没有实际的资源的,所以在我们使用引用类型的变量之前,首先想一想他们引用的是谁,这个对象的另一个别称是谁。
否则你可能错把引用理解成了实际的变量,从而导致代码的错误
一个很常见的错误就是在一个函数中,返回对局部变量的引用
虽然返回后,这个引用还存在,但是他所引用的对象已经在函数退出时析构掉了,所以对这个引用变量解引用所产生的行为是未定义的
用一个图来解释一下值类别(value catagory)
这里我们只看下面三个就可以,lvalue是左值,xvalue是将亡值,prvalue是纯右值
左值就是我们等号左边的值,可以被取地址的值
纯右值就是没名字的值,一般是字面量或者临时对象
将亡值就是即将被销毁的值,也可以看做是有名字的纯右值,std::move
就是将左值的类型转换为了将亡值,因为他是有名字的右值
有一个规则要注意,就是临时对象的生命周期延长规则
如果一个prvalue被绑定到一个引用上,那么他的生命周期会延长到和这个引用变量一样长
但是这条规则对xvalue,也就是将亡值是不适用的
但是这是为什么呢?我的理解是这样的
右值引用本质还是引用,需要一个绑定到一个右值上
但是对于将亡值来说,他有自己的生命周期,我们不能简单的让右值引用去随意改变他的生命周期
而对于纯右值来说,他的生命周期是这条语句,所以我们就可以把这个纯右值绑定到我们的右值引用上
那既然如此,我们引入将亡值的目的是什么呢?或者用std::move
将一个左值转换为将亡值的目的是什么呢?
调用移动构造函数,注意移动构造函数接收的是右值,我们这里说的也是右值引用,而不是什么将亡值引用或者纯右值引用
看上面的图也可以看到,右值分为纯右值和将亡值
所以我们在使用类似Point(1, 1)
或者Point(std::move(point1))
的时候,我们使用的是移动构造函数,这样也就消除了中间临时变量造成的冗余拷贝
然后再来说一说函数返回引用的问题
之前有讲到过,返回对局部变量的引用是错误的,但是这不代表我们不可以返回引用的类型
比如一个对象
struct Beta {
Beta_ab ab;
Beta_ab const& getAB() const& { return ab; }
Beta_ab && getAB() && { return move(ab); }
};
我们在使用类似Beta ab = Beta().getAB()
的时候,使用的就是移动构造
因为这里我们将ab变为将亡值,作为右值引用返回给了调用者,调用者会调用移动构造函数处理之前将亡值的资源
后面的修饰符用来说明函数作为右值时才调用
再看这个情况
class Widget {
private:
DataType values;
public:
DataType& data() & { return values; }
DataType data() && { return std::move(values); }
};
auto values = makeWidget().data();
可以发现和上面的返回类型是不同的
这里我们返回的是值,而不是引用
在这个过程中,由于我们返回的是move(ab)
,所以临时返回值会首先通过移动构造函数创建,然后回到调用者,makeWidget().data()
是纯右值,所以我们会再次调用移动构造函数,创建values
其实应该还可以让函数直接返回值,然后我们使用右值引用捕捉这个临时的返回值
不过实际在使用的过程中,因为我们有返回值优化,所以不需要自己主动的添加std::move
,这样还会多进行一次移动构造
有关万能引用和完美转发我们下次再说,这里简单总结一下
分析或者写代码时,我们主要看好std::move
和函数参数
move帮我们生成将亡值,用于调用移动构造函数或者移动赋值
纯右值自己就可以调用,所以不需要我们主动去move
最后还有一个比较有意思的结论,对引用的引用仍然还是引用,因为我们的引用是别名,这个结论在右值引用下仍然成立
函数返回右值引用,(返回引用也是)需要返回的是非局部变量,那么这个非局部变量就需要我们主动去使用move,否则的话他不会被判定为右值
文章评论