## 为什么需要移动语义
### 指针成员和拷贝构造
当一个类中含有指针成员时,由于默认的拷贝构造函数只会进行浅拷贝(memcpy),所以当我们写出一下代码时:
```cpp
class Base {
public:
Base() :data(new int(0)) {}
//Base(const Base& base): data(base.data){} 默认的拷贝构造
~Base() {
cout << "~Base()" << endl;
delete data;
}
void out() {
cout << data << ":" << *data << endl;
}
private:
int* data;
};
int main() {
Base base1;
{
Base base2(base1);
base1.out(); // address1:0
base2.out(); // address2:0
} // ~Base(),base2析构
base1.out(); // address1:未知
return 0;
}
```
由于 base2 只是拷贝了 base1 的指针,所以它们的指针地址是相同的,当 base2 析构之后,所指向的内存地址已经被释放,当 base1 再去调用的时候已经是野指针了,会造成未知后果。出现这种情况,我们只需要自己重新实现拷贝构造函数重新申请堆空间即可。
```cpp
Base(const Base& base): data(new int(*base.data)){}
```
### 移动语义
上一部分说到重新实现拷贝构造,但是这样还会一些问题,比如下面的例子:
```cpp
class Test {
public:
Test(int a = 0) {//普通构造函数
d = new int(a);
cout << "构造函数\n";
}
Test(const Test& tmp) {//拷贝构造函数
d = new int;
*d = *(tmp.d);
cout << "拷贝构造函数\n";
}
~Test() {//析构函数
if (d != NULL) {
delete d;
cout << "delete d\n";
}
cout << "析构函数\n";
}
int* d;
};
Test GetTmp() {
Test h; //调用构造函数
cout << "Resource from " << __func__ << ": " << (void*)h.d << endl;
return h; //调用拷贝构造,将 h 复制给一个临时变量,然后 h 被析构
}
int main() {
Test obj = GetTmp(); //再次调用拷贝构造,将临时的对象赋值给 obj,临时对象被析构
cout << "Resource from " << __func__ << ": " << (void*)obj.d << endl;
return 0;
}
```
上述代码输出如下:

在编译时需要添加`-fno-elide-constructors`, 此选项的作用是在 g++ 上编译时关闭 [`RVO`](https://blog.csdn.net/rlyhaha/article/details/80381170)。编译器会对返回值进行优化,简称RVO,是编译器的一项优化技术,它涉及(功能是)消除为保存函数返回值而创建的临时对象。
通过上面的例子看到,**临时对象的维护 ( 创建和销毁 ) 对性能有严重影响**。
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。
移动语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。通过移动语义,临时对象中的资源能够转移其它的对象里。
## 移动语义定义
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现移动语义,需要定义移动构造函数,还可以定义移动赋值操作符。对于右值的拷贝和赋值会调用移动构造函数和移动赋值操作符。
普通的函数和操作符也可以利用右值引用操作符实现转移语义。
### 移动构造函数
```cpp
class Base {
public:
Base():d(new int(3)) {
cout << "Construct: " << ++n_cstr << endl;
}
Base(const Base& h) :d(new int(*h.d)) {
cout << "Copy construct: " << ++n_cptr << endl;
}
Base(const Base&& h) :d(h.d) {
this->d = nullptr;
cout << "Move construct: " << ++n_mvtr << endl;
}
~Base() {
delete this->d;
cout << "Destruct: " << ++n_dstr << endl;
}
public:
int* d;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int Base::n_cstr = 0;
int Base::n_dstr = 0;
int Base::n_cptr = 0;
int Base::n_mvtr = 0;
Base GetTemp() {
Base temp;
cout << "Resource from" << __func__ << ": " << hex << temp.d << endl;
return temp;
}
int main() {
Base a = GetTemp();
cout << "Resource from" << __func__ << ": " << hex << a.d << endl;
return 0;
}
```
上述代码输出如下

调用了两次移动构造,没有调用拷贝构造,且 `GetTemp()` 中的 `h.d` 与 `main` 中的 `h.d` 指向的是同一片地址空间,成功的拜托了对内存的重新拷贝赋值、析构带来的性能浪费。有了右值引用和转移语义,我们在设计和实现类时,对于需要动态申请大量资源的类,应该设计移动构造函数和移动赋值函数,以提高应用程序的效率。
移动构造和拷贝构造函数类似,有几点需要注意:
- 参数(右值)的符号必须是右值引用符号,即 `&&`
- 参数(右值)不可以是常量,因为我们需要修改右值
- 参数(右值)的资源链接和标记必须修改,否则,右值的析构函数就会释放资源,转移到新对象的资源也就无效了。
### 移动赋值函数
```cpp
class Base {
public:
Base():d(new int(3)) {
cout << "Construct: " << ++n_cstr << endl;
}
Base(const Base& h) :d(new int(*h.d)) {
cout << "Copy construct: " << ++n_cptr << endl;
}
Base(Base&& h) :d(h.d) {
h.d = nullptr;
cout << "Move construct: " << ++n_mvtr << endl;
}
Base& operator=(const Base& tmp) {
if (&tmp == this)
return *this;
delete d;
d = new int(*tmp.d);
cout << "赋值运算符重载函数" << endl;
}
Base& operator=(Base&& tmp) {
this->d = tmp.d;
tmp.d = nullptr;
cout << "移动赋值函数" << endl;
return *this;
}
~Base() {
delete this->d;
cout << "Destruct: " << ++n_dstr << endl;
}
public:
int* d;
static int n_cstr;
static int n_dstr;
static int n_cptr;
static int n_mvtr;
};
int Base::n_cstr = 0;
int Base::n_dstr = 0;
int Base::n_cptr = 0;
int Base::n_mvtr = 0;
Base GetTemp() {
Base temp;
cout << "Resource from" << __func__ << ": " << hex << temp.d << endl;
return temp;
}
int main() {
Base a;
a = GetTemp();
cout << "Resource from" << __func__ << ": " << hex << a.d << endl;
return 0;
}
```
C++11:移动语义