Modern C++

  1. 右值引用
  2. 右值与左值
  3. 移动语义

右值引用

右值引用是 Moving semantics 和 Prefect forwarding 的基石:

Moving semantics:
将内存所有权从一个对象转移到另一个对象, 可以替换效率低下的复制. 使用 move constructor 和 move assignment operator 来实现.

Prefect forwarding:
定义一个 template function, 可以接受任意类型参数, 将参数转发给目标函数, 保证目标函数接受的参数类型与传递给 template function 的类型相同.

右值与左值

C++ 11 中, 每个表达式有两个属性: type(除去引用特性,用于类型检查) 和 value category(用于语法检查)

value category 有三个基本类型: lvalue、 prvalue、xrvalue. 后两者统称 rvalue. 比较好的区分方法就是: 可以取地址的值就是左值, 不是左值的就是右值.

区分xrvalue 和 prvalue:

prvalue(pure rvalue): 表达式产生的中间值

xrvalue: 通过右值引用产生的值

右值引用的作用: 延长初始化对象的生命周期.

写法:

1
2
3
4
5
6
7
8
9
int&& rr = 200;
rr = 100; // 右值引用被初始化后就称为左值.

void foo(int&& val) {
cout << value;
}
int main() {
foo(200);
}

PS: const 引用可以接收右值, 在做参数时, 如果没有接收右值引用的重载版本, const 引用参数可以接收右值在引用.

移动语义

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
template <typename T>
class DynamicArray {
public:
explicit DynamicArray(int size): _size(size), array { new T[size] } {
cout << "Constructor: dynamic array is created!\n";
}
~DynamicArray() {
delete[] array;
cout << "Destructor: dynamic array is destroyed!\n";
}
DynamicArray(const DynamicArray& rhs): _size(rhs.size()) {
array = new T[_size];
for (int i = 0; i < size(); ++i) {
array[i] = rhs.array[i];
}
cout << "Copy constructor: dynamic array is created!\n";
}

DynamicArray& operator=(DynamicArray rhs) {
cout << "Copy assignment operator is called\n";
swap(rhs);
return *this;
}
void swap(DynamicArray& rhs) {
this->_size = rhs.size();
this->array = new T[rhs.size()];
for (int i = 0; i < rhs._size; ++i) {
array[i] = rhs.array[i];
}
}

T& operator[] (int index) {
// 无边界检查
return array[index];
}
const T& operator[] (int index) const {
return array[index];
}
int size() const { return _size; }
private:
T* array;
int _size;
};

DynamicArray<int> arrayFactor(int size) {
DynamicArray<int> arr{ size };
return arr;
}

int main() {
{
DynamicArray<int> arr = arrayFactor(10);
}
}

/*
Constructor: dynamic array is created!
Destructor: dynamic array is destroyed!

*/

上述代码中, 原本是在 Factor 函数中通过构造函数创建一个临时对象, 然后返回用 copy assignment 来初始化 arr. 其中 copy assignment 的实现方法是直接传值, 然后用 swap 函数交换参数的内容. 但是经clang优化成了 直接对 arr 用普通构造函数来创建一个动态数组… //todo

用VS跑出的结果如下

1
2
3
4
Constructor: dynamic array is created!
Copy constructor: dynamic array is created!
Destructor: dynamic array is destroyed!
Destructor: dynamic array is destroyed!

可以看到, 对象被创建了两次, 原本是先调用构造函数, 内部创建一个动态数组对象, 然后返回这个动态数组, 因为这个动态数组是内部的, 所以仍要再创建一个临时对象, 然后初始化arr的方式是调用拷贝构造函数, 但是编译器这里还是进行了优化, 直接拿内部的动态数组去初始化arr.最后两个在生命周期结束后被析构,但是最理想的情况是只申请一份内存, 然后转移这份内存, 因此就需要移动构造函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DynamicArray {
public:
...
DynamicArray(DynamicArray&& rhs): size(rhs._size), array(rhs.array) {
rhs._size = 0;
rhs.array = nullptr;
cout << "Move constructor: dynamic array is move!\n";
}
DynamicArray& operator=(DynamicArray&& rhs) {
cout << "Move assignment operator is called\n";
if (this == &rhs) {
return *this;
}
delete[] m_array;
m_size = rhs.m_array;
m_array = rhs.m_array;
rhs.m_size = 0;
rhs.m_array = nullptr;

return *this;
}

};
1
2
3
4
Constructor: dynamic array is created!
Move constructor: dynamic array is moved!
Destructor: dynamic array is destroyed!
Destructor: dynamic array is destroyed!

VS下跑出的结果就是调用了移动构造函数, 将申请的内存的控制权直接转移,调用了两次析构函数是因为临时对象印染存在, 只不过第一次析构的是个 nullptr ,当然这对程序没什么影响.

script>