【C++ 】【move 、移动语义】左值、右值;左值引用、右值引用;移动语义;move的使用;实现资源让渡;配合unique_ptr;

12 篇文章 2 订阅
4 篇文章 0 订阅


一、左值/右值?

1.1 定义

左值与右值(lvalue/rvalue)这两概念是从 c 中传承而来的。
在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式)。
右值指的则是只能出现在等号右边的变量(或表达式)。

右值不能当成左值使用,但左值可以当成右值使用。

C++中其实是左值表达式与右值表达式。其中:表达式是有值的,值是有类型的,值是动态的,类型是静态的。例如:

int &a = b; a 是一个左值,他的类型是左值引用,指向一个左值。
int &&c = fun(); c是一个左值,他的类型是右值引用,指向一个右值。

如果一个函数的返回类型是左值引用,那么调用这个函数得到的返回值将是一个 lvalue[N3690,3.10.1],之所以特别地说这个事,是因为如果一个函数的返回值不是引用类型,那调用这个函数得到的结果将是一个临时变量,是个右值,而且是纯右值(prvalue)

1.2 右值能修改吗?

下图运算操作所产生的中间结果是右值;
在这里插入图片描述

但是string类型却是可以的。这是C++相较C独有的。因为string 不是基础类型而是一个类,其实只是调用了成员函数”operator=“。

定义如下:

  1. 对于基础类型,右值是不可被修改的,也不可被 const, volatile 所修饰

  2. 对于自定义的类型,右值却允许通过它的成员函数进行修改。

其实就是:右值调用成员函数是被允许的,但成员函数有可能不是 const 类型,因此通过调用右值的成员函数,也就可能会修改了该右值。不知道是不是设计初衷。//疑问点

在这里插入图片描述

二、左值引用/右值引用?

2.1引入右值引用、常量引用的用法;

引入右值引用之前:引用只能指向左值,常量引用可以指向右值和左值,这就导致没有专门为右值做的引用。即下图对比:(右值可以被const 的引用指向,而不可以被引用指向。)

在这里插入图片描述

这样就可以用一个基类的引用指向一个子类的临时变量,然后通过这个引用来实现多态,但又不用处理子类的销毁。

class Base
 {
     public:  virtual void print() const{ cout << "base print()" << endl; }
 };
 
class DerOne: public Base
 {
     public: virtual void print() const { cout << "DerOne print()" << endl; }
 };
 
 class DerTwo: public Base
 {
     public: virtual void print() const { cout << "DerTwo print()" << endl; }
 };
 Base GetBase()
 {
     return Base();
 }
 
 DerOne GetDerOne()
 {
      return DerOne();
 }
 
 DerTwo GetDerTwo()
 {
      return DerTwo();
 }
 int main()
 {
     const Base& ref1 = GetBase();
     const Base& ref2 = GetDerOne();
     const Base& ref3 = GetDerTwo();
     ref1.print();
     ref2.print();
     ref3.print();
     return 0;
 }

实现效果如下:
实现效果
注意:const 对象只能调用const 成员函数。

自从C++ 11 引入右值引用以后,以前的引用就称之为左值引用。

int &a = b; a 是一个左值,他的类型是左值引用,指向一个左值。
int &&c = fun(); c是一个左值,他的类型是右值引用,指向一个右值。

2.2 引入右值引用的意义/目的

为了解决一系列的问题C++ 11引入了右值引用,用&&表示。从而引入了move(), forward() 等。
目的有两个:

一个是为自定义类型实现 move 语义;一个是配合 forwarding reference 来实现完美转发。

移动语义可以减少无谓的内存拷贝,要想实现移动语义,需要实现移动构造函数和移动赋值函数。

三、move/forward?

  • std::move()将一个左值转换成一个右值,强制使用移动拷贝和赋值函数,这个函数本身并没有对这个左值什么特殊操作。
  • std::forward()和universal references通用引用共同实现完美转发。
  • 用empalce_back()替换push_back()增加性能。
  • std::move() 的主要作用是将一个左值转为 xvalue, 它的实现,本质上就是一个 static_cast<>。
  • 而 std::forward() 则是用来配合 forwarding reference 实现完美转发,它主要的作用是将一个类型为引用(左值引用或右值引用)的左值(引用这个变量本身),转化为它的类型所对应的值类型

3.1、move 语义的使用,实现资源让渡

  • move 语义的使用。实现了资源让渡的过程
	std::string foo = "foo-string";
	std::string bar = "bar-string";
	std::vector<std::string> myvector;

	myvector.push_back(foo);                    // copies
	myvector.push_back(std::move(bar));         // moves

	std::cout << "myvector contains:";
	for (std::string& x : myvector) std::cout << ' ' << x;
	
	std::cout << '\n';
	cout << foo << endl;
	cout << bar << endl;
  • 实现结果:发现 bar中的便来给你已经被让渡到vector中去了
    在这里插入图片描述

3.2、move 语义配合 移动构造 与 移动赋值运算符

  • move 兼顾了深拷贝与浅拷贝的优点;

  • 带move 的是调用了移动构造、移动赋值运算符

	String s1("Hello");                          // 构造函数
	cout << s1 << endl;
	String s2 = s1;                             // 调用拷贝构造函数
	String s2(s1);                                  // 调用拷贝构造函数
	cout << s2 << endl;
	String s2A(std::move(s1));              // 移动构造函数
	cout << s2A << endl;
	String s3;                                        // 无参构造函数
	cout << s3 << endl;
	s3 = s2;                                           // 调用赋值函数
	cout << s3 << endl;
	String s3A;                                      // 无参构造函数
	s3A = std::move(s2A);                    // 移动赋值运算符
	cout << s3A << endl;
  • 移动构造和移动赋值运算符 的实现:
  1. delete 自己的堆空间;(构造的话,本身不存在数据,少了一步delete)
  2. 将指针指向需要的空间中的地址块;
  3. 将对方的指针指向NULL;
// 移动构造函数
String::String(String&& other)
{
	if (other.m_data != NULL)
	{
		// 资源让渡
		m_data = other.m_data;
		other.m_data = NULL;
	}
}
// 移动赋值运算符
String& String::operator=(String&& rhs)noexcept
{
	if(this != &rhs)
	{
		delete[] m_data;
		m_data = rhs.m_data;
		rhs.m_data = NULL;
	}
	return *this;
}

3.3、move 语义配合unique_ptr 智能指针

  • 简单的过继内存空间;实现资源让渡;
int main() {
											   // foo   bar    p
										   // ---   ---   ---
	std::unique_ptr<int> foo;                // null
	std::unique_ptr<int> bar;                // null  null
	int* p = nullptr;                        // null  null  null

	foo = std::unique_ptr<int>(new int(10)); // (10)  null  null
	bar = std::move(foo);                    // null  (10)  null
	p = bar.get();                           // null  (10)  (10)
	*p = 20;                                 // null  (20)  (20)
	p = nullptr;                             // null  (20)  null

	foo = std::unique_ptr<int>(new int(30)); // (30)  (20)  null
	p = foo.release();                       // null  (20)  (30)
	*p = 40;                                 // null  (20)  (40)

	std::cout << "foo: ";
	if (foo) std::cout << *foo << '\n'; else std::cout << "(null)\n";

	std::cout << "bar: ";
	if (bar) std::cout << *bar << '\n'; else std::cout << "(null)\n";

	std::cout << "p: ";
	if (p) std::cout << *p << '\n'; else std::cout << "(null)\n";
	std::cout << '\n';

	delete p;   // the program is now responsible of deleting the object pointed to by p
				// bar deletes its managed object automatically

	return 0; 
}

在这里插入图片描述

  • 简单的切换作用域:
	unique_ptr<string> p0;
	{
		unique_ptr<string> p1(new string());
		p0 = std::move(p1);
	}

参考

std::move

std::unique_ptr

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值