c++右值引用
首先是左值和右值
左值:在内存中有固定内存的(能取地址)
右值:内存中没有固定内存,属于将亡值
(这里只是简单理解,c++11实际的值类别会更复杂一些)
需要注意字符串常量属于左值,是在程序数据段中的。
看一个很有意思的例子
++i;
i++;
++i是i+1再返回本身,属于左值;i++是将本身拷贝一份做临时值,最后返回的也是这个临时值,所以属于右值。
再看一个函数返回值的例子
int x = 1; int get_val() { return x; }
int main()
{
int y = get_val();
return 0;
}
函数get_val返回的x是全局变量,属于左值,但函数结束返回时会发生一次拷贝,将x拷贝到一个临时变量中,最后在调用处接收到的值属于右值。其实这里在不考虑编译器优化的情况下,x不管是全局变量、局部变量还是参数,返回时都要拷贝一次。
左值引用与右值引用:
左值引用就是我们传参时经常写的那个引用,
int get_val(int &x) { return x; }
其中,非常量左值引用必须引用左值,而常量左引用可以引用左值也可以引用右值
int &x = 7;//编译出错 const int &x = 11;//编译通过
第二行与const int x = 11;从效果上看没有什么差别,但是含义不同,引用是延长了11的生命周期,而直接赋值是11在语句执行完毕后就被销毁。
这个特性对于函数的形参有很大的作用,比如复制构造函数与复制赋值运算符函数
如果必须能接受左值和右值类型,则必须加上const属性,这样一来就没有办法做修改了,解决方法是右值引用。
右值引用是变量名前加&&,必须引用到右值上,可以延长右值的生命周期。
#include <iostream> class { public: X(){} X(const X &x){}
X(X &&x){}
~X(){} } X make_x() { X x1; return x1; } int main() { X x1 = make_x(); X &&x2 = make_x();
X x3(make_x());
return 0; }
main函数中,x1对象从调用make_x()开始一共是有3次构造,make_x()中局部变量x1一次构造,返回时复制到临时变量中有一次复制构造,main中接收返回的临时值又是一次复制构造,一共三次。
可以看出来对于复制构造函数,存在拷贝的开销,如果说对象非常大,且需要频繁的创建,那么时间开销是不小的,还会产生大量的内存碎片。解决方法我们继续往下看。
x2对象在接收make_x返回值之前和x1都一样,接收返回值时由于是右值引用,所以这里少了一次复制构造。
x3调用的构造器是移动构造器,接收的是一个右值,移动构造器中不会进行拷贝,只是简单将make_x返回的临时变量挪给x3用,这样就又少了一次拷贝。
(这里需要注意的是main中有赋值构造函数,它会调用拷贝构造函数)
移动语义存在的问题:如果在移动的过程中发生异常,导致只移动了一部分,产生的结果是无法预测的,需要确保移动期间不会发生异常,加一个noexcept。
万能引用:
template<class T> void bar(T &&t) int get_val(){return S} auto &&y = get_val();
当“&&”发生类型推导时,“&&”就代表万能引用;否则代表右值引用。
诸如以下类型的都是右值引用
template<typename T> void f(std::vector<T>&& param); // “&&” means rvalue reference template<typename T> void f(const T&& param); // “&&” means rvalue reference
甚至是这种
template <class T, class Allocator = allocator<T> > class vector { public: ... void push_back(T&& x); // fully specified parameter type ⇒ no type deduction; ... // && ≡ rvalue reference };
由于vector在声明时会指定模板类型,所以这里不存在类型推导,因此这里的T&&只是右值引用。
待续。。。

浙公网安备 33010602011771号