移动语义是C++11引入的一项重要特性,它通过右值引用来实现资源的转移,从而避免不必要的拷贝操作。与传统的拷贝构造函数和赋值运算符不同,移动构造函数和移动赋值运算符可以在对象内部将资源“搬迁”到新的实例,而不是创建新的资源副本。
右值引用的基本语法
T&& ref = std::move(obj);
std::move 并不真正移动对象,它只是把对象转换为右值,允许调用移动构造函数或移动赋值运算符。
实现移动构造函数
class Buffer {
public:
Buffer(size_t sz) : size(sz), data(new char[sz]) {}
// 移动构造
Buffer(Buffer&& other) noexcept : size(other.size), data(other.data) {
other.size = 0;
other.data = nullptr;
}
// 复制构造
Buffer(const Buffer& other) : size(other.size), data(new char[other.size]) {
std::memcpy(data, other.data, size);
}
~Buffer() { delete[] data; }
private:
size_t size;
char* data;
};
移动构造函数把 other 的资源指针直接接管,然后把 other 的指针置为空,确保 other 的析构不会重复释放同一块内存。
实现移动赋值运算符
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data; // 先释放旧资源
size = other.size;
data = other.data; // 接管新资源
other.size = 0;
other.data = nullptr;
}
return *this;
}
移动赋值运算符在接管新资源前需要先释放自身已有资源,防止内存泄漏。
使用场景
- 临时对象
std::vector <int> createVector() { std::vector <int> v = {1, 2, 3, 4, 5}; return v; // 通过 NRVO 或移动构造返回 } - 容器元素
当把自定义对象放入std::vector时,容器会在内部移动元素以提升性能。
性能收益
- 对于大对象(如包含动态数组、文件句柄等)而言,移动可以将拷贝成本降到 O(1)。
- 编译器在可行时会自动生成移动构造函数和移动赋值运算符,若用户显式声明了拷贝构造/赋值,则移动成员需要手动实现。
注意事项
- 不可移动对象:若类内部持有不可移动资源(如
std::mutex),需要删除移动构造/赋值运算符,或使用std::unique_ptr包装。 - 异常安全:移动构造函数和移动赋值运算符最好声明为
noexcept,以满足 STL 容器的要求。 - 右值引用与 lvalue 引用的区别:右值引用只能绑定到右值,不能绑定到 lvalue;但通过
std::move可以强制转换。
通过合理使用移动语义和右值引用,C++ 开发者可以显著提升程序性能,减少内存拷贝,尤其在高频率数据处理和资源密集型应用中表现尤为突出。