移动语义
文章目录
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(); // 一次构造(容器内直接创建)