1、产生背景
背景:在程序运行的过程中,会产生大量的临时对象。临时对象只在某一个表达式执行的过程中创建,表达式执行完毕时,临时对象马上就被销毁。其作用是用来进行过渡的,造成了资源拷贝的不必要的浪费。
需求:希望将临时对象直接转移到新对象中
问题:临时对象是右值,只能通过 const 引用来绑定,但 const 引用同时又可以绑定到左值。C++11之前,当传递参数时,const 引用是无法判断传递过来的参数是左值还是右值(所以const引用又称万能引用)。需要在语法层能够唯一确定传递过来的参数是右值。
解决:C++11 引入右值引用来优化性能,具体来说通过移动语义避免临时对象拷贝,通过 move 语义将临时对象转移到其他对象中,通过 forward 完美转发来解决不能按照参数实际类型来转发的问题。
2、右值引用
左值 vs 右值
- 左值:表示对象的身份,可以取地址,持久的状态。
- 右值:表示对象的值,不能取地址,例如:临时对象、字面常量都是右值
右值引用:只能绑定到右值的引用,指向将要被销毁的对象,也就意味着可以自由地接管所引用对象的资源。
变量是左值,不能将一个右值引用绑定到一个变量上,即使这个变量是是右值引用类型。
int &&rr1 = 42; // 正确,字面值常量是右值 int &&rr2 = rr1; // 错误,表达式 rr1 是左值,右值引用变量仍然是个左值。
3、std::move
添加编译选项: -fno-elide-constructors
唯一功能:强制将左值转换为右值,等同于 static_cast<T &&>(lvaule)
,没有性能上的提升。将左值转换为右值后,左值就不能直接使用了。
std::move()
作用于内置类型没有任何作用,内置类型本身是左值还是右值,经过移动后不会改变,对于自定义类型效果不错。
4、移动语义函数
移动语义可以将资源通过浅拷贝方式从一个对象转移到另一个对象,只是转移,没有内存拷贝,从而减少临时对象的创建、拷贝和销毁。传递右值,优先调用移动语义的函数,即移动语义函数的执行优先于复制控制语义函数的执行。
移动语义
移动操作通常不抛出异常,若不抛出异常,则必须将其标记为 noexcept,显式告诉标准库可以安全使用。移动操作后,移动后源对象必须保持有效的、可析构的状态,但用户不能对其值做出任何假设。
只有当一个类没有定义任何版本的拷贝控制成员,且所有数据成员都能移动构造或移动赋值时,编译器才会为它合成移动构造函数或移动赋值运算符。
移动构造函数
- 浅拷贝
- 转移后将其置为 nullptr
// 移动构造函数:右值引用作为函数参数 String(String &&rhs) noexcept // 1、浅拷贝 : _pstr(rhs._pstr) { // 2、移动后,销毁源对象,这里将其设为 nullptr rhs._pstr = nullptr; }
移动赋值运算符函数
- 自移动
- 释放左操作数
- 浅拷贝
- 返回 this 指针
// 移动赋值运算符函数:右值引用作为函数参数 String &operator=(String &&rhs) noexcept { // rhs 右值引用,拥有名字的变量,在函数内部是一个左值 // 1、自移动 if(this != &rhs) { // 2、释放左操作数 delete [] _pstr; _pstr = nullptr; // 3、浅拷贝 _pstr = rhs._pstr; rhs._pstr = nullptr; } // 4、返回*this return *this; }
例:实现内置类型 string 类
#include <string.h> #include <iostream> #include <vector> using std::cout; using std::endl; using std::vector; class String { public: friend std::ostream & operator<<(std::ostream &os, const String&); String() : _pstr(new char[1]()) { cout << "String()" << endl; } String(const char * pstr) : _pstr(new char[strlen(pstr) + 1]()) { cout << "String(const char*)" << endl; strcpy(_pstr, pstr); } String(const String & rhs) : _pstr(new char[strlen(rhs._pstr) + 1]()) { cout << "String(const String &)" << endl; strcpy(_pstr, rhs._pstr); } 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) noexcept : _pstr(rhs._pstr) { cout << "String(String&&)" << endl; rhs._pstr = nullptr; } String & operator=(String && rhs) noexcept { cout << "String& operator=(String&&)" << endl; if(this != &rhs) { delete [] _pstr; _pstr = rhs._pstr; rhs._pstr = nullptr; } return *this; } ~String() { cout << "~String()" << endl; if(_pstr){ delete [] _pstr; _pstr = nullptr; } } private: char * _pstr; }; std::ostream & operator<<(std::ostream &os, const String& rhs) { os << rhs._pstr; return os; }
5、forward 转发
forward
完美转发:参数在传递过程中保持其值属性的功能,即在参数传递过程中,左值传递后仍是左值,右值仍是右值,根据参数的类型转发给正确的函数。
6、emplace_back
就地构造对象,只调用一次构造函数,避免了内存的拷贝和移动。
push_back(); // 两次构造(临时对象创建,容器内部创建),一次析构(临时对象析构)。 emplace_back(); // 一次构造(容器内直接创建)