Modern C++
- 右值引用
- 右值与左值
- 移动语义
右值引用
右值引用是 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); } }
|
上述代码中, 原本是在 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>