std::move

前置知识

左值:表达式结束后依然存在的持久对象(在内存中占有确定位置)

右值:表达式结束时不再存在的临时对象(不在内存中占有确定位置)

1
2
int val; // 对变量val进行了定义,故在栈上会给val分配内存地址
val = 4; // “=”要求左边是可修改的左值,4是临时参与运算的值,一般在寄存器上暂存

std::move函数

  • 将一个左值转换成右值引用,从而可以调用右值引用的拷贝构造函数
  • 针对有在堆上为对象分配内存的情况
  • 该函数仅仅做了类型转换(可理解为static_cast转换),对性能无影响。
  • 真正的移动操作在移动构造函数或者移动赋值操作符中发生,我们可以在自己的类中实现移动语义,避免深拷贝
  • 被move的值有没有失效,关键看有没有调用移动构造函数,或者移动复制运算符
    • 单纯的Foo && f = std::move (x); 是不会调用移动构造或者移动赋值运算符的。 只是把右值给绑定到了f上。
    • Foo t = std::move (x), 这样才会调用移动构造函数

Why std::move

右值引用和std::move广泛应用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能

在没有右值引用之前,一个简单的数组类通常实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}

// 深拷贝构造
Array(const Array& temp_array) {
size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}

// 深拷贝赋值,不可避免
Array& operator=(const Array& temp_array) {
delete[] data_;

size_ = temp_array.size_;
data_ = new int[size_];
for (int i = 0; i < size_; i ++) {
data_[i] = temp_array.data_[i];
}
}

~Array() {
delete[] data_;
}

public:
int *data_;
int size_;
};

改进:提供一个移动构造函数,把被拷贝者的数据移动过来,之后丢弃被拷贝者,从而避免深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Array {
public:
Array(int size) : size_(size) {
data = new int[size_];
}

// 深拷贝构造
Array(const Array& temp_array) {
...
}

// 深拷贝赋值
Array& operator=(const Array& temp_array) {
...
}

// 移动构造函数,可以浅拷贝
Array(const Array& temp_array, bool move) {
data_ = temp_array.data_;
size_ = temp_array.size_;
// 为防止temp_array析构时delete data,提前置空其data_
temp_array.data_ = nullptr;
}


~Array() {
delete [] data_;
}

public:
int *data_;
int size_;
};

这样做有三个问题:

  1. 不优雅,表示移动语义需要一个额外的参数
  2. temp_array是const左值引用,无法被修改为nullptr
  3. 即使把函数参数改成非const Array& temp_array,由于左值引用不能接右值,无法使用Array a = Array(Array(), true);

最终,使用右值引用

1
2
3
4
5
6
int main(){
Array a;

// 左值a,用std::move转化为右值
Array b(std::move(a));
}

std::move在STL容器中的应用

在STL的很多容器中,都实现了以右值引用为参数移动构造函数移动赋值重载函数,或者其他函数

最常见的如std::vector的push_backemplace_back。参数为左值引用意味着拷贝,为右值引用意味着移动。

有些STL类只有移动构造函数,比如unique_ptr,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能深拷贝

Reference

https://www.cnblogs.com/shadow-lr/p/Introduce_Std-move.html