天天看点

右值引用和对象移动右值引用对象移动

右值引用

右值

C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象。 所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。 右值是指临时的对象,它们只在当前的语句中有效。

右值引用概念

为了支持移动操作,C++11引入了一种新的引用类型-右值引用。就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。右值引用只能绑定到一个将要销毁的对象。

由于右值引用只能绑定到临时对象,我们得知:

所引用的对象将要被销毁

该对象没有其他用户

这两个特性意味着:使用右值引用的代码可以自身自由的接管所引用的对象的资源。

变量可以看作只有一个运算对象而没有运算符的表达式。

int i = 23;
	int &&j = i;//错误:不能将一个右值引用绑定到一个左值上
	int &&k = i * 3;//正确,i*3是一个右值
           

标准库move函数

move函数理解之前先要知道左值、左值引用、右值、右值引用的概念。

虽然不能将一个右值引用直接绑定到一个左值上,但可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。

int i=6;
int &&l=std::move(i);
           

 对比下面操作

第二段代码使用了move语句,而第一段代码没有使用move语句。输出的结果差异也很明显,第二段代码中,原来的字符串st已经为空,而第一段代码中,原来的字符串st的内容没有变化。

int main()
{
	string st = "I'm a superman";
	vector<string> vc;
	vc.push_back(st);
	cout << vc[0] << endl;
	if (!st.empty())
		cout << st << endl;
	system("pause");
	return 0;
}
           

右值引用和对象移动右值引用对象移动

int main()
{
	string st = "I'm a superman";
	vector<string> vc;
	vc.push_back(move(st));
	cout << vc[0] << endl;
	if (!st.empty())
		cout << st << endl;
	system("pause");
	return 0;
}
           
右值引用和对象移动右值引用对象移动

对象移动

提出

在很多情况下都会发生对象拷贝,比如利用reallocate重新分配内存的过程,在其中某些情况下,对象拷贝后就立即被销毁了。在这些情况下,移动而非拷贝对象会大幅度提升性能。

移动构造函数和移动赋值运算符

移动构造函数,参数是右值引用

StrVec::StrVec(StrVec &&s) noexcept                 //noexcept指名不抛出异常
:elements(s.elements), first_free(s.first_free),cap(s.cap)
{
            s.elements = s.first_free = s.cap = nullptr;  //销毁地动源后的对象
}
           

与拷贝构造函数不同,移动构造函数不分配任何新内存:它接管给定的StrVec中的内存。在接管内存后,它将给定对象中的指针都置为nullptr。

合成的移动操作

与处理拷贝构造函数和拷贝赋值运算符一样,编译器也会合成移动构造函数和移动赋值运算符。但是,合成移动构造的条件与合成拷贝操作的条件大不相同。

只有当一个类没有定义任何自己版本的拷贝控制成员(拷贝构造函数、拷贝赋值运算符、析构函数),且它的所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。

如果一个类定义了一个移动构造函数或一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符被定义为删除的。

右值引用和成员函数

除了构造函数和赋值运算符之外,成员函数也可以同时提供拷贝版本和移动版本。例如:

void push_back(const X&);    //拷贝:绑定到任意类型的X
void push_back(X&&);    //移动:只能绑定到类型X的可修改的右值
           

关于此部分讲解很好的博文:https://www.cnblogs.com/qingergege/p/7607089.html

move源码实现(转载自:原文地址)

预备知识

1.引用折叠规则:

X& + & => X&

X&& + & => X&

X& + && => X&

X&& + && => X&&

2.函数模板参数推导规则(右值引用参数部分):当函数模板的模板参数为T而函数形参为T&&(右值引用)时适用本规则。

若实参为左值 U& ,则模板参数 T 应推导为引用类型 U& 。

(根据引用折叠规则, U& + && => U&, 而T&& ≡ U&,故T ≡ U& )

若实参为右值 U&& ,则模板参数 T 应推导为非引用类型 U 。

(根据引用折叠规则, U或U&& + && => U&&, 而T&& ≡ U&&,故T ≡ U或U&&,这里强制规定T ≡ U )

3.std::remove_reference为C++0x标准库中的元函数,其功能为去除类型中的引用。

   std::remove_reference<U&>::type ≡ U

   std::remove_reference<U&&>::type ≡ U

   std::remove_reference<U>::type ≡ U

4.以下语法形式将把表达式 t 转换为T类型的右值(准确的说是无名右值引用,是右值的一种)

static_cast<T&&>(t)

5.无名的右值引用是右值

  具名的右值引用是左值。

6.注:本文中 ≡ 含义为“即,等价于“。

std::move

函数功能:

std::move(t) 负责将表达式 t 转换为右值,使用这一转换意味着你不再关心 t 的内容,它可以通过被移动(窃取)来解决移动语意问题。

源码与测试代码

template<typename _Tp>
    inline typename std::remove_reference<_Tp>::type&&
    move(_Tp&& __t)
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
           
#include<iostream>
using namespace std;

struct X {};

int main()
{
	X a;
	X&& b = move(a);
	X&& c = move(X());
}
           

代码说明:

  1. 测试代码第9行用X类型的左值 a 来测试move函数,根据标准X类型的右值引用 b 只能绑定X类型的右值,所以 move(a) 的返回值必然是X类型的右值。
  2. 测试代码第10行用X类型的右值 X() 来测试move函数,根据标准X类型的右值引用 c 只能绑定X类型的右值,所以 move(X()) 的返回值必然是X类型的右值。
  3. 首先我们来分析 move(a) 这种用左值参数来调用move函数的情况。
  4. 模拟单步调用来到源码第3行,_Tp&& ≡ X&, __t  ≡ a 。
  5. 根据函数模板参数推导规则,_Tp&& ≡ X& 可推出 _Tp ≡ X& 。
  6. typename std::remove_reference<_Tp>::type ≡ X 。
  7. typename std::remove_reference<_Tp>::type&& ≡ X&& 。
  8. 再次单步调用进入move函数实体所在的源码第4行。
  9. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(a)
  10. 根据标准 static_cast<X&&>(a) 将把左值 a 转换为X类型的无名右值引用。
  11. 然后我们再来分析 move(X()) 这种用右值参数来调用move函数的情况。
  12. 模拟单步调用来到源码第3行,_Tp&& ≡ X&&, __t  ≡ X() 。
  13. 根据函数模板参数推导规则,_Tp&& ≡ X&& 可推出 _Tp ≡ X 。
  14. typename std::remove_reference<_Tp>::type ≡ X 。
  15. typename std::remove_reference<_Tp>::type&& ≡ X&& 。
  16. 再次单步调用进入move函数实体所在的源码第4行。
  17. static_cast<typename std::remove_reference<_Tp>::type&&>(__t) ≡ static_cast<X&&>(X())
  18. 根据标准 static_cast<X&&>(X()) 将把右值 X() 转换为X类型的无名右值引用。
  19. 由9和16可知源码中std::move函数的具体实现符合标准,
  20. 因为无论用左值a还是右值X()做参数来调用std::move函数,
  21. 该实现都将返回无名的右值引用(右值的一种),符合标准中该函数的定义。

继续阅读