C++11:移动语义

移动语义

1、产生背景

背景:在程序运行的过程中,会产生大量的临时对象。临时对象只在某一个表达式执行的过程中创建,表达式执行完毕时,临时对象马上就被销毁。其作用是用来进行过渡的,造成了资源拷贝的不必要的浪费。

需求:希望将临时对象直接转移到新对象中

问题:临时对象是右值,只能通过 const 引用来绑定,但const引用同时又可以绑定到左值。C++11之前,当传递参数时,const引用是无法判断传递过来的参数是左值还是右值(所以const引用又称万能引用)。需要在语法层能够唯一确定传递过来的参数是右值。

解决:C++11 右值引用,并增加移动语义函数。

2、左值、右值

  • 左值:可以取地址的表达式,

  • 右值:不能取地址的表达式,临时对象、匿名对象、临时变量、字面值常量都是右值

3、右值引用

右值引用:只能绑定到右值不能绑定到左值。右值引用有名字的是左值,无名字的是右值。也可以说作为函数返回值的 && 是右值,声明出来的 && 是左值,即引用在等号左边是左值,在右边是右值。

int && ref = 10; //正确
int && ref = a; //error

3.1、std::move

添加编译选项: -fno-elide-constructors

唯一功能:强制将左值转换为右值,等同于 static_cast<T &&>(lvaule),没有性能上的提升。将左值转换为右值后,左值就不能直接使用了。

std::move()作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过移动后不会改变,对于自定义类型效果不错。

4、移动语义函数

移动语义可以将资源通过浅拷贝方式从一个对象转移到另一个对象,只是转移,没有内存拷贝,这样能够减少不必要的临时对象的创建、拷贝以及销毁。

在这里插入图片描述

传递右值,优先调用移动语义的函数,即移动语义函数的执行优先于复制控制语义函数的执行。

4.1、移动构造函数

  • 浅拷贝
  • 转以后将其置为 nullptr
// 移动构造函数:右值引用作为函数参数
// 如果使用右值引用,意味着被修改,因为右值马上就要被销毁
String(String &&rhs)
: _pstr(rhs._pstr) { // 1、浅拷贝
    // 2、转移后,将其设为nullptr
    // 防止临时对象被销毁时,把它所拥有的资源回收了,不能达到转移资源的目的
    rhs._pstr = nullptr;
    cout << "String(String &&)" << endl;
}

4.2、移动赋值运算符函数

  • 自移动
  • 释放左操作数
  • 浅拷贝
  • 返回 this 指针
// 移动赋值运算符函数
String &operator=(String &&rhs) {
    // rhs绑定的是一个右值,但rhs本身在函数内部是左值(因为其有名字)
    cout << "String &operator=(String &&)" << endl;
    //1、自移动
    if(this != &rhs) {
        //2、释放左操作数
        delete [] _pstr; 
        _pstr = nullptr;
        //3、浅拷贝
        _pstr = rhs._pstr; 
        rhs._pstr = nullptr;
    }
    //4、返回*this
    return *this;
}

4.3、实现内置类型 string 类

#include <string.h>

#include <iostream>
#include <vector>
using std::cout;
using std::endl;
using std::vector;

class String {
public:
	String()
	: _pstr(new char[1]())
	{	cout << "String()" << endl;	}

	/* explicit */
	String(const char * pstr)
	: _pstr(new char[strlen(pstr) + 1]()) {
		strcpy(_pstr, pstr);
		cout << "String(const char*)" << endl;
	}

	// 拷贝构造函数:const万能引用,无法分辨左值和右值
	String(const String & rhs)
	: _pstr(new char[strlen(rhs._pstr) + 1]()) {
		strcpy(_pstr, rhs._pstr);
		cout << "String(const String &)" << endl;
	}
	
    // 赋值运算符函数
	String & operator=(const String & rhs) {
		cout << "String & operator=(const String&)" << endl;	
		if(this != &rhs) {
			delete [] _pstr;
			_pstr = new char[strlen(rhs._pstr) + 1]();
			strcpy(_pstr, rhs._pstr);
		}
		return *this;
	}

	// 移动构造函数
	String(String && rhs)
	: _pstr(rhs._pstr)  {   
		rhs._pstr = nullptr;
		cout << "String(String&&)" << endl;
	}
    
	// 移动赋值运算符函数
	String & operator=(String && rhs) {	
		cout << "String& operator=(String&&)" << endl;
		if(this != &rhs) {
			delete [] _pstr;
			_pstr = rhs._pstr;
			rhs._pstr = nullptr;
		}
		return *this;
	}

	~String() {
		if(_pstr){
			delete [] _pstr;
			_pstr = nullptr;
		}
		cout << "~String()" << endl;
	}

	friend std::ostream & operator<<(std::ostream &os, const String&);
private:
	char * _pstr;
};

std::ostream & operator<<(std::ostream &os, const String& rhs) {
	os << rhs._pstr;
	return os;
}

5、forward 转发

参数在传递过程中保持其值属性的功能,即在参数传递过程中,左值传递后仍是左值,右值仍是右值,根据参数的类型转发给正确的函数。

在这里插入图片描述

6、emplace_back

容器内部就地构造对象,只调用一次拷贝构造函数,避免了内存的拷贝和移动。

  • push_back():调用两次拷贝构造,第一次是临时对象的创建,第二次是容器内创建对象;一次析构,临时对象析构。
  • emplace_back():调用一次拷贝构造,容器内直接创建对象,没有临时对象的析构。
push_back(); // 两次构造(临时对象创建,容器内部创建),一次析构(临时对象析构)。
emplace_back(); // 一次构造(容器内直接创建)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值