在什么样的背景下提出来的?
在程序运行过程中,会产生大量临时对象,临时对象只在某一个表达式执行的过程中创建,执行完毕时,临时对象被马上销毁。本质上只是用来进行过度的,这种带来了资源拷贝上的不必要的浪费。特别是当对象包含大量数据时
移动语义通过引入移动构造函数和移动赋值运算符,可以避免不必要的深拷贝,大幅提高程序的性能。
需求
我们希望临时对象在拷贝时能直接转移到新对象中。
解决方案
C++11提出了右值引用
左值与右值的概念
左值:可以取地址
右值:不可以取地址。包括临时变量、临时对象(string("world"))、字面值常量。
左值引用与右值引用
左值引用:左值引用可以绑定到左值,但不能绑定到右值。
int &ref= a;
int &ref=10;//error
const左值引用:const左值引用既可以绑定到左值,也可以绑定到右值
const int &ref=a;
const int &ref=10;
右值引用:右值引用可以绑定到右值,但不能绑定到左值
int &&ref=10
int &&ref=a;//error
类中增加2个函数
1,具有拷贝控制语义的函数
拷贝构造函数,赋值运算符函数
2,具有移动语义的函数
1)移动构造函数与移动赋值运算符函数,编译器不会自动提供,必须要手写
2)针对右值而言,移动构造函数优先于拷贝构造函数的执行
3)深拷贝—>浅拷贝
移动构造函数
class String
{
public:
String()
: _pstr(nullptr)
{
cout << "String()" << endl;
}
//针对于右值而言,移动构造函数优先于拷贝构造函数的执行
//
//移动构造函数,移动语义,
//将String("world")申请的堆空间直接转移给s3的数据成员_pstr
//String s3 = String("world")
String(String &&rhs)
: _pstr(rhs._pstr)
{
cout << "String(String &&)" << endl;
rhs._pstr = nullptr;
}
~String()
{
cout << "~String()" << endl;
if(_pstr)
{
delete [] _pstr;
_pstr = nullptr;
}
}
private:
char *_pstr;
};
移动赋值运算符函数
#include <string.h>
#include <iostream>
using std::cout;
using std::endl;
class String
{
public:
String()
: _pstr(nullptr)
{
cout << "String()" << endl;
}
//赋值运算符函数
String &operator=(const String &rhs)
{
cout << "String &operator=(const String &)" << endl;
if(this != &rhs) //1、防止自复制
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;
_pstr = new char[strlen(rhs._pstr) + 1]();//3、深拷贝
strcpy(_pstr, rhs._pstr);
}
return *this;//4、返回*this
}
/* s2 = std::move(s2); */
//移动赋值运算符函数
//s2 = String("world")
String &operator=(String &&rhs)
{
cout << "String &operator=(String &&)" << endl;
if(this != &rhs) //1、防止自移动
{
delete [] _pstr;//2、释放左操作数
_pstr = nullptr;
_pstr = rhs._pstr;//3、浅拷贝
rhs._pstr = nullptr;
}
return *this;//4、返回*this
}
~String()
{
cout << "~String()" << endl;
if(_pstr)
{
delete [] _pstr;
_pstr = nullptr;
}
}
private:
char *_pstr;
};
总结
左右值的概念,左值引用右值引用的概念。
拷贝构造函数与赋值运算符函数,编译器会自动提供;但是移动构造函数与移动赋值运算符函数,编译器不会自动提供,必须要手写。
将拷贝构造函数与赋值运算符函数称为具有拷贝控制语义的函数;将移动构造函数与移动赋值运算符函数称为具有移动语义的函数。
移动语义的函数优先于拷贝语义的函数。