右边值引用
右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。它的主要目的有两个方面:
消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
能够更简洁明确地定义泛型函数。
1. 左值与右值的区别
右值引用和左值引用的区别:
- 左值可以寻址,而右值不可以。
- 左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
- 左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。
int a = 0; // a是左值,0是右值
int b = rand(); // b是左值,rand()是右值
//左值有名称,可根据左值获取其内存地址,而右值没有名称,不能根据右值获取地址。
右值引用作用:大部分时间和左指引用一样,但是有一点区别,他能够绑定到临时变量(右值)
延长了右值的生命周期。
类的右值是一个临时对象,如果没有被绑定到引用,在表达式结束时就会被废弃。于是我们可以在右值被废弃之前,移走它的资源进行废物利用,从而避免无意义的复制。
(1)避免拷贝,提高性能,实现move(),避免无意义复制 、析构开销减少
(2)避免重载参数的复杂性,实现forward()
2. 引用叠加规则、右值引用的特殊类型推断规则
左值引用A&和右值引用A&& 可相互叠加
即:X& &、X& &&、X&& &都折叠成X&
X&& &&折叠为X&&
A& + A& = A&
A& + A&& = A&
A&& + A& = A&
A&& + A&& = A&&
举例示例,void foo(T&& x)中,如果T是int&, x为左值语义,如果T是int&&, x为右值语义。
右值引用的特殊类型推断规则:当将一个左值传递给一个参数是右值引用的函数,且此右值引用指向模板类型参数(T&&)时,编译器推断模板参数类型为实参的左值引用,如:
template<typename T>
void f(T&&);
int i = 42;
f(i)
//上述的模板参数类型T将推断为int&类型,而非int。
3. std::move的作用
减少一次资源的创建与释放。
而是仅仅复制了指针,并将源对象的指针置空。因为源对象是一个右值,不会再被使用。这样的构造函数被称为“转移(移动)构造函数”。
一个左值转换为右值。如此可以让对象可以在转移语义中使用。
//如果类X包含一个指向某资源的指针,在左值语义下,类X的赋值构造函数如下:
X::X(const X& other)
{
// ....
// 销毁资源
// 复制other的资源,并使指针指向它
// ...
}
//应用代码如下,其中,tmp被赋给a之后,便不再使用。
X tmp;
// ...经过一系列初始化...
X a = tmp;
//首先执行一次默认构造函数(tmp申请资源),再执行一次复制构造函数(a复制资源),
//最后退出作用域时再执行一次析构函数(tmp释放资源)。
//--------------------------------------------------------------------------------------
如下实现move:
X::X(const X& other)
{
// 交换this和other的资源
}
4. std::move的实现
std::move用于强制将左值转化为右值。其实现方式如下:
template<class T>
typename remove_reference<T>::type&&
std::move(T&& a) noexcept
{
typedef typename remove_reference<T>::type&& RvalRef;
return static_cast<RvalRef>(a);
}
当a为int左值(右值)时,根据引用叠加原理,T为int&, remove_reference<T> = int, std::move返回类型为int&&,即右值引用。
std::move(string("hello"))调用解析:
- 首先,根据模板推断规则,确地T的类型为string;
- typename remove_reference<T>::type && 的结果为 string &&;
- move函数的参数类型为string&&;
- static_cast<string &&>(t),t已经是string&&,于是类型转换什么都不做,返回string &&;
string s1("hello"); std::move(s1); 调用解析:
- 首先,根据模板推断规则,确定T的类型为string&;
- typename remove_reference<T>::type && 的结果为 string&
- move函数的参数类型为string& &&,引用折叠之后为string&;
- static_cast<string &&>(t),t是string&,经过static_cast之后转换为string&&, 返回string &&;
5. 完美转发
完美转发实现了参数在传递过程中保持其值属性的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。
6. std::forward()
std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
class Foo
{
public:
std::string member;
template<typename T>
Foo(T&& member): member{std::forward<T>(member)} {}
};
6. 对比
- std::move执行到右值的无条件转换。就其本身而言,它没有move任何东西。
- std::forward只有在它的参数绑定到一个右值上的时候,它才转换它的参数到一个右值。
- std::move和std::forward只不过就是执行类型转换的两个函数;std::move没有move任何东西,std::forward没有转发任何东西。在运行期,它们没有做任何事情。它们没有产生需要执行的代码,一byte都没有。
- std::forward<T>()不仅可以保持左值或者右值不变,同时还可以保持const、Lreference、Rreference、validate等属性不变。