栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++11右值引用和移动构造

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++11右值引用和移动构造

C++11右值引用和移动构造
  • 1. 基本概念
  • 2. 移动构造和移动赋值
  • 3. 完美转发
  • 参考资料

1. 基本概念
类型释义区别例子
左值表示数据的表达式可以取地址、可以赋值变量、解引用的指针(特殊:const修饰的变量不可被赋值)
右值表示数据的表达式不能取地址字面常量、表达式返回值、传值返回的返回值
左值引用给左值取别名左值引用不可直接引用右值
(const除外)
int a; int &b = a;
右值引用给右值取别名右值引用不可直接引用左值
(move除外)
int a = 1; int b = 2; int&& c = a + b;
#include 

int add(int a, int b)
{
	return a + b;
}
int main()
{
	//左值 a、b、c、p、*p
	int a = 1, b = 2;
	const int c = 3;
	int* p = &a;

	//右值 1、a+b、add(a, b)
	int d = a + b;
	int e = 1;
	int f = add(a, b);
    
	// 左值引用+const 可以引用右值
	const int& a1 = 1;
	const int& a2 = a + b;
	const int& a3 = add(a, b);

	//右值引用可以引用move后的左值
	int&& b1 = std::move(a);
	const int&& b2 = std::move(c);
	int*&& b3 = std::move(p);
	int&& b4 = std::move(*p);

	return 0;
}

补充:

  • 右值中自定义类型的临时对象、匿名对象称为将亡值;将亡值在调用结束后就会被销毁,但若用一个const变量指向这个将亡值,则将亡值的生命周期将延长到与const变量相同的生命周期。

  • 右值不可取地址,但是右值的别名可以取地址(右值引用后,会将右值存到新特定的空间,这个空间地址是可以被获知的);右值引用是一个左值,可以被赋值。
int&& a = 1;
std::cout << &a << std::endl;
a = 2;
std::cout << a << std::endl;
2. 移动构造和移动赋值
  • 出了作用域对象还在,则可以使用左值引用返回;若出作用域后对象不在了,则不能用左值引用返回(对临时对象{一般在上层调用函数的栈或寄存器中}的引用会造成非法空间的访问),只能传值返回,但是当传值有深拷贝时会加大内存和计算的开销——解决方法:增加移动构造。当函数需要返回一个对象的时候(返回对象是一个右值),会优先调用对象的移动构造。

  • 临时对象维护一块堆上的空间,当临时对象析构后堆上的空间也要释放,移动构造保证了临时对象释放前,将资源交换给新的对象,避免了堆空间的重复申请和释放。

  • 对象构造时调用拷贝构造还是移动构造,关键看被拷贝的对象是一个左值还是右值,左值调用拷贝构造;右值调用移动构造。

  • std::move将一个左值move成右值。

实例代码:

class Value
{
public:
	Value(const size_t size = 10)
		:_size(size)
		, _p(new int[_size] {66})
	{
		std::cout << "Value Constructor->" << this << "____" << this->_p;
		if (this->_p)
			printf("_____%d", *(int*)(this->_p));

		std::cout << std::endl;

	}
	Value(const Value& v)
	{
		int size = v.GetDataSize();
		//int size = v._size;  //这里有个小问题,为什么private的成员变量可以被调用?
		std::cout << size << std::endl;

		//需要写深拷贝,否则浅拷贝的p指向同一块地址空间,会被析构两次
		_p = new int[v._size]{33};
		std::cout << "Value Copy Constructor->" << this << "____" << this->_p;
		if(this->_p)
			printf("_____%d", *(int*)(this->_p));
		std::cout <<  std::endl;

	}

	Value& operator=(const Value& v)
	{
		_size = 0;
		_p = nullptr;

		std::cout << "赋值运算符重载" << std::endl;
		Value temp(v);
		Swap(temp);
		return *this;
	}

	//移动赋值
	Value& operator=(Value&& v)
	{
		_size = 0;
		_p = nullptr;

		std::cout << "移动赋值" << std::endl;
		Swap(v);
		return *this;
	}

	//移动构造
	Value(Value&& v)
		:_size(0)
		,_p(nullptr)
	{
		Swap(v);   //将右值引用的新空间换给类对象,实现计算的资源的缩减
		std::cout << "移动构造:";
		std::cout << "Value Copy Constructor->" << this << "____" << this->_p;
		if (this->_p)
			printf("_____%d", *(int*)(this->_p));
		std::cout << std::endl;
	}

	~Value()
	{
		std::cout << "Value Deconstructor->" << this << "____" << this->_p;
		if (this->_p)
			printf("_____%d", *(int*)(this->_p));

		if (_p)
		{
			delete[] _p;
			_p = nullptr;
		}
		std::cout << std::endl;
	}

	size_t GetDataSize() const
	{
		return _size;
	}

private:

	void Swap(Value& v)
	{
		swap (_p, v._p);
		swap(_size, v._size);
	}
private:
	size_t _size;
	int* _p;
};

Value func()
{
	Value v;
	return v;   // 返回对象是一个左值,调用拷贝构造一个临时对象;临时对象是要给右值,给新对象赋值时调用移动构造
				//编译器优化后会直接move(v),调用移动构造:v出了作用域会被析构,它的资源可以利用。
}

void SetValue(const Value& v)
{
	std::cout << "#####  SetValue(const Value& v) #####" << std::endl;
}

void SetValue(Value&& v)
{
	std::cout << "#####  右值引用 #####" << std::endl;
}

int main()
{
	//Value v = func();  //func()函数返回了一个Value的对象
	//SetValue(v);

	//std::cout << std::endl;
	//SetValue(func());

	Value v1;
	Value v2 = std::move(v1);  //左值move后变右值,移动构造后原本不是将亡值的对象(v1)会被改变,造成调用出错。

	//Value v3;
	//v3 = v2;

	Value v4;
	v4 = std::move(v2);
	return 0;
}
3. 完美转发
  • 使用std::forward让一个右值保留原来的属性,能够传递给一个右值引用的函数。这样右值传参时就可以调用移动语义节省计算资源。
template
void Fun(T&t)
{
	std::cout << "T&t" << std::endl;
}

template
void Fun(T&& t)
{
	std::cout << "T&&t" << std::endl;
}

template
void Forward(T&& t)  //模板的T&&称为万能引用
{
	//Fun(std::forward(t));
	Fun(t);  //若不用forward,则t被右值引用后是一个左值,会调用左值引用
}


int main()
{

	//	左值 a、b、c、p、*p
	int a = 1, b = 2;
	const int c = 3;
	int* p = &a;

	Forward(a);
	Forward(c);
	Forward(*p);

	//右值 1、a+b、add(a, b)
	Forward(1);
	Forward(add(a, b));
	Forward(a + b);

	return 0;
}

结果显示:

使用了std::forward(t)的结果

T&t
T&t
T&t
T&&t
T&&t
T&&t

不使用的结果:

T&t
T&t
T&t
T&t
T&t
T&t
参考资料

右值和右值引用

移动构造

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/994376.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号