C++11中的移动语义
一、核心概念
左值与右值:
- 左值(Lvalue):可以取地址的对象(有名字的变量),如
int a = 5;
中的a
。 - 右值(Rvalue):临时对象或字面量(无名字),如
5
、表达式a + b
的结果、函数返回的临时对象。
- 左值(Lvalue):可以取地址的对象(有名字的变量),如
移动语义:
当操作右值(临时对象)时,不复制其资源(如堆内存),而是直接 “窃取” 资源的所有权,避免不必要的内存分配 / 释放,提升效率。
二、移动构造函数
1. 定义
移动构造函数是一种特殊的构造函数,参数为当前类的右值引用,用于从右值对象 “移动” 资源到新对象。
class MyString {
private:char* data;int length;public:// 移动构造函数(参数为右值引用)MyString(MyString&& other) noexcept : data(other.data), length(other.length) {// 标记原对象资源为“空”,避免析构时重复释放other.data = nullptr;other.length = 0;}
};
2. 特点
- 参数必须是右值引用(
&&
),且通常不应该是const
(因为需要修改原对象)。 - 通常用
noexcept
修饰(告诉编译器此函数不会抛出异常,便于容器优化)。 - 作用:接管右值对象的资源(如堆内存、文件句柄),原对象会被置为 “空状态”(避免析构冲突)。
三、移动赋值运算符
1. 定义
移动赋值运算符用于将一个右值对象的资源 “移动” 给已存在的对象,类似于移动构造,但针对的是赋值操作。
class MyString {
public:// 移动赋值运算符MyString& operator=(MyString&& other) noexcept {if (this != &other) { // 避免自赋值// 释放当前对象的资源delete[] data;// 接管右值对象的资源data = other.data;length = other.length;// 标记原对象资源为“空”other.data = nullptr;other.length = 0;}return *this;}
};
2. 特点
- 与移动构造类似,参数为右值引用,通常用
noexcept
修饰。 - 需先释放当前对象的资源,再接管右值对象的资源,最后处理原对象。
四、与拷贝构造 / 赋值的区别
操作 | 拷贝构造 / 赋值 | 移动构造 / 赋值 |
---|---|---|
参数 | 常量左值引用(const T& ) | 右值引用(T&& ) |
行为 | 复制资源(深拷贝) | 转移资源所有权(不复制) |
原对象状态 | 保持不变 | 被修改为 “空状态”(如指针置空) |
适用场景 | 左值对象(有名字的变量) | 右值对象(临时对象) |
五、问题
何时会调用移动构造 / 赋值?
- 当实参是右值时(如临时对象、
std::move
转换的左值)。
例:
MyString a; MyString b = std::move(a); // 调用移动构造(a被转为右值) b = MyString("temp"); // 调用移动赋值(临时对象是右值)
- 当实参是右值时(如临时对象、
std::move
的作用?- 并非 “移动” 资源,而是将左值强制转换为右值引用,让编译器优先选择移动语义。
- 注意:
std::move
后原对象可能变为 “空状态”,不应再使用(除非重新赋值)。
默认移动函数的生成规则?
- 若用户未定义任何拷贝构造、拷贝赋值、移动构造、移动赋值或析构函数,编译器会自动生成默认移动函数。
- 若定义了拷贝操作(拷贝构造 / 赋值),编译器不会自动生成移动函数(需手动定义)。
- 若定义了移动函数,默认拷贝函数会被删除(需显式定义才可用)。
为什么移动函数通常用
noexcept
?- 标准容器(如
vector
)在扩容时,若元素的移动构造是noexcept
,会使用移动语义;否则会使用拷贝(避免移动中抛异常导致数据丢失)。 - 加上
noexcept
可提升容器操作的效率。
- 标准容器(如
移动语义的优势?
- 避免临时对象的深拷贝,减少内存分配 / 释放开销(尤其对大对象,如字符串、容器)。
- 例:函数返回大对象时,编译器可通过移动语义避免拷贝(返回值优化 RVO 的补充)。
如何禁用移动语义?
- 显式删除移动函数:
MyString(MyString&&) = delete;
- 或定义拷贝函数(编译器不再生成默认移动函数)。
- 显式删除移动函数:
六、经典场景示例
#include <iostream>
#include <utility> // for std::moveclass Resource {
private:int* data;
public:Resource() : data(new int[1000]) { std::cout << "构造函数:分配资源\n"; }// 移动构造Resource(Resource&& other) noexcept : data(other.data) {other.data = nullptr;std::cout << "移动构造:转移资源\n";}// 移动赋值Resource& operator=(Resource&& other) noexcept {if (this != &other) {delete[] data;data = other.data;other.data = nullptr;std::cout << "移动赋值:转移资源\n";}return *this;}~Resource() {if (data) delete[] data;std::cout << "析构函数:释放资源\n";}
};int main() {Resource a; // 构造:分配资源Resource b = std::move(a); // 移动构造:b接管a的资源(a变为空)Resource c;c = std::move(b); // 移动赋值:c接管b的资源(b变为空)return 0;
}
输出:
构造函数:分配资源
移动构造:转移资源
构造函数:分配资源
移动赋值:转移资源
析构函数:释放资源
析构函数:释放资源
析构函数:释放资源