右值引用
C++11标准中引入了一种新的引用类型——右值引用(rvalue reference),右值引用必须绑定到一个右值上,用&&来获取右值引用。
右值
左值表示一个对象的身份,如:
返回左值引用的函数、下标、解引用、前置递增/递减运算符
而右值表示一个对象的值
返回非引用类型的函数、算数运算符、位运算、逻辑运算符、后置递增/递减运算符
我们看出右值有以下特点
- 右值没有其他用户
右值是无名的,只在他存在的语句中使用,过后甚至都无法再找到它 - 右值即将被销毁
右值是一个字面值或临时变量,使用过后资源就被释放
右值的右值引用不是右值
int &&rr1 = 43; //字面值是一个右值,声明一个右值引用来绑定它
int &&rr2 = rr1; //错误,rr1是一个左值
rr1是一个右值引用,但它本身却不是一个右值,原因是rr1是一个变量,有名字并且有着和其他变量一样的生成期,并不是一个临时对象,所以rr1是一个左值。
拷贝和移动(资源)
类经常会管理一部分资源,形如
class A
{
public:
string *ps;
//构造函数,真正分配资源
A(const string &_s = "hello"):ps(new string(_s)) {
cout << "constructed" << endl;
}
//析构函数,释放资源
~A()
{
delete(ps);
cout << "destructed" << endl;
system("pause");
}
//拷贝构造函数,真正分配资源
A(const A& rhs):ps(new string(*rhs.ps)) {
cout << "copy" << endl;
}
};
new产生的内存就是我们所称的资源,在用一个类的对象初始化另一个对象时,会执行类的拷贝构造函数(根据拷贝行为的不同又分为深拷贝和浅拷贝,浅拷贝只负责源对象控制资源的指针,并不赋值资源,与源对象共享资源)。
在对象的赋值时也会有类似的行为。
有了右值的概念后,我们发现在用一个临时对象初始化一个类对象或者为其赋值时,这个临时对象和它的资源会被拷贝,而执行完这个语句后,临时对象及其资源就会被销毁,这样就造成了不必要的操作,反正临时对象也是即将被销毁的,我们可否不销毁临时对象的资源,也不拷贝这个资源,而是直接利用这个资源?
于是就有了移动操作——当构造函数或赋值运算符的对象是一个右值时,我们直接把右值的资源“盗取”过来
//移动构造函数
A(A&& rhs):ps(rhs.ps) {
rhs.ps = nullptr;
}
这个移动构造函数做了两件事:
- 让构造对象的ps指针指向临时变量的资源,构造对象并没有重新new自己的资源
- 把临时对象的ps指针置为空,临时变量销毁时可以安全的析构,不会把那块资源销毁
std::move
std::move只是返回一个对象的右值引用,用来触发类中定义的移动构造函数、移动赋值运算符
//move在标准库中的实现
template<class _Ty>
constexpr remove_reference_t<_Ty>&&
move(_Ty&& _Arg) _NOEXCEPT
{ // forward _Arg as movable
return (static_cast<remove_reference_t<_Ty>&&>(_Arg));
}
移动赋值运算符
用一个临时对象为类的对象赋值时,同样可以使用移动操作来避免不必要的拷贝。
//移动赋值操作
A& A::operator=(A &&rhs)
{
this->ps = rhs.ps;
rhs.ps = nullptr;
}
实际上还有一个更加通用和安全的写法
A& A::operator=(A rhs)
{
//类中定义的void swap(const A&,cosnt A&)操作
//并非std::swap
//
swap(*this,rhs);
return *this;
}
void A::swap(const A& lhs,const A& rhs)
{
using std::swap;
swap(lhs.ps,rhs.ps);
}
operator=接受一个A的对象而不是引用,在参数传递时会构造一个临时对象
这样的操作是安全的,因为临时对象会正常的析构
在传参时的那一次构造也合理的,根据实参是左值还是右值,会调用拷贝或是移动构造函数
- 如果是左值,左值的资源不应该被销毁,这一次拷贝必不可少
- 如果是右值,调用移动构造函数并没有分配新的资源,不会造成浪费