10.Move Semantic
一、lvalues 和 rvalues 简介
- lvalue(左值):具有持久存储地址的表达式或变量,可以位于赋值符号的左侧。
- rvalue(右值):代表临时对象或无法标识的值,常出现在赋值符号的右侧,不能被赋值。
1.1 例子
1 |
|
1.2 生命周期差异
- lvalue 的生命周期取决于其作用域。
- rvalue 的生命周期通常在表达式结束时即结束,除非被存储或绑定到引用延长。
1.3 lvalue 和 rvalue 引用
- lvalue 引用(
T&
):绑定到 lvalues。 - rvalue 引用(
T&&
):专门绑定到 rvalues。 - const lvalue 引用(
const T&
):可以绑定到 lvalues 和 rvalues,常用于传递参数。
问题解答:对于 auto&& v4 = v1 + v2;
,尽管 v4
绑定了一个 rvalue,v4
本身作为一个引用是 lvalue。
二、移动操作
移动语义允许从临时对象转移资源至新对象,减少资源的不必要复制,从而提高性能。
2.1 特殊成员函数
- 移动构造函数:从存在的 rvalue 创建新对象。
- 移动赋值操作符:用存在的 rvalue 替换现有对象。
2.2 示例
1 |
|
注意:拷贝构造函数和拷贝赋值操作符的参数是
const T&
,因此它们适用于 lvalues 和 rvalues。但如果你实现了一个接受T&&
参数的移动构造函数和移动赋值操作符,那么编译器将为 rvalues 优先选择T&&
。
三、std::move
简介
当执行移动操作时,正确使用 std::move
是实现有效移动语义的关键,因为它确保了资源能够被正确地移动而非拷贝。
3.1 移动操作的常见误区
让我们以 RandomBag
类为例来探讨一个常见的误区。RandomBag
类包含一个类型为 vector<int>
的成员变量 elems
。
1 |
|
如果在移动赋值操作中直接赋值而不使用 std::move
,如下所示:
1 |
|
这里的误区在于,尽管 rhs
是 r-value 引用,但未经 std::move
处理,rhs.elems
在赋值时被视为 l-value,导致执行的是拷贝而非预期的移动操作。
3.2 正确使用 std::move
为了确保资源被移动,我们应该这样做:
1 |
|
通过 std::move
,rhs.elems
被转换为 r-value,触发 vector<int>
的移动赋值操作,从而实现资源的有效移动。重要的是要记住,std::move
本身不移动任何东西;它仅将其参数转换为 r-value,真正的移动操作依赖于移动构造函数或移动赋值操作符。
四、举例:通用的 swap 函数
1 |
|
五、规则总结
- 规则五:如果你显式定义(或删除)了拷贝构造函数、拷贝赋值、移动构造函数、移动赋值或析构函数中的任何一个,你应该为所有五个定义(或删除)。
- 规则零:如果默认操作符工作正常,则不要定义自定义的特殊成员函数。
1 |
|
六、示例代码
10.Move Semantic
https://ci-tz.github.io/2024/02/12/10-Move-Semantic/