C++移动构造函数是C++11标准新增的特殊构造函数,它接收同类型的右值引用作为参数,用于将临时对象的资源直接转移到新对象中,而不是像拷贝构造函数那样重新分配资源并复制内容,从而减少不必要的性能开销。

移动构造函数的基本概念
在C++11之前,当对象发生拷贝时,即使原对象是一个即将被销毁的临时对象,也会触发深拷贝操作,造成资源浪费。移动构造函数就是为了解决这个问题而设计的,它针对右值(比如临时对象、被std::move转换后的对象)生效,实现资源的“窃取”而非复制。
移动构造函数的典型声明形式如下:
class MyClass {
private:
int* data;
int size;
public:
// 拷贝构造函数
MyClass(const MyClass& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
};
上面的代码中,移动构造函数将other对象的data指针和size直接赋值给新对象,然后将other的data置为nullptr,避免两个对象析构时重复释放同一块内存。
移动构造函数的核心特点
- 参数类型是同类型的右值引用,即
ClassName&&形式 - 通常不会分配新的资源,而是直接接管原对象的资源
- 原对象在移动后处于有效但未指定的状态,一般不再被使用
- 默认情况下,如果用户没有定义移动构造函数,编译器可能会自动生成,但前提是类中没有定义拷贝构造函数、拷贝赋值运算符、移动赋值运算符和析构函数
移动构造函数的异常安全性问题
异常安全性是指当构造函数执行过程中抛出异常时,不会导致资源泄漏或者程序处于不一致的状态。移动构造函数的异常安全性尤为重要,因为标准库容器在进行扩容等操作时,会优先使用移动构造函数,如果移动构造函数抛出异常,可能会导致容器操作失败甚至未定义行为。
为什么移动构造函数需要保证异常安全
如果移动构造函数可能抛出异常,那么在容器进行元素移动的过程中,一旦某个元素的移动构造抛出异常,已经移动的元素可能处于无效状态,后续的处理会变得非常复杂。因此C++标准建议移动构造函数尽量标记为noexcept,告知编译器和标准库该构造函数不会抛出异常,这样标准库在进行容器操作时可以放心使用移动语义,而不是回退到拷贝操作。
确保移动构造函数异常安全的方法
要保障移动构造函数的异常安全,核心原则是让移动构造函数的操作尽量不抛出异常,通常可以通过以下方式实现:
- 对移动构造函数添加
noexcept说明符,明确声明该函数不会抛出异常。如果移动过程中确实可能抛出异常,需要重新设计移动逻辑,避免异常发生。 - 移动构造函数的操作仅涉及指针赋值等不会抛出异常的操作,不要在其中进行内存分配、文件打开等可能失败的操作。如果必须分配资源,应当使用不会失败的方式,或者在分配失败时直接终止程序,避免资源泄漏。
- 如果移动构造函数中需要执行可能抛出异常的操作,应当先完成这些操作,再修改原对象的状态,确保即使抛出异常,原对象的状态也不会被破坏。
下面是一个符合异常安全要求的移动构造函数示例:
#include <utility>
#include <cstddef>
class SafeMoveClass {
private:
int* buffer;
std::size_t length;
public:
SafeMoveClass(std::size_t len) : length(len) {
buffer = new int[len]; // 这里可能抛异常,但属于普通构造函数,不影响移动构造
}
// 拷贝构造函数
SafeMoveClass(const SafeMoveClass& other) : length(other.length) {
buffer = new int[other.length];
std::copy(other.buffer, other.buffer + other.length, buffer);
}
// 异常安全的移动构造函数,标记为noexcept
SafeMoveClass(SafeMoveClass&& other) noexcept : buffer(other.buffer), length(other.length) {
other.buffer = nullptr;
other.length = 0;
}
// 析构函数
~SafeMoveClass() {
delete[] buffer;
}
// 移动赋值运算符也建议标记为noexcept
SafeMoveClass& operator=(SafeMoveClass&& other) noexcept {
if (this != &other) {
delete[] buffer;
buffer = other.buffer;
length = other.length;
other.buffer = nullptr;
other.length = 0;
}
return *this;
}
};
移动构造函数的使用场景
移动构造函数通常在以下场景中被自动调用:
- 用临时对象初始化新对象时,比如
MyClass obj = MyClass(); - 使用
std::move将一个左值转换为右值引用后初始化新对象,比如MyClass obj2 = std::move(obj1); - 标准库容器进行扩容、元素交换等操作时,会优先使用移动构造函数移动元素
常见注意事项
移动构造函数执行后,原对象不再拥有被转移的资源,此时不应该再访问原对象的对应成员,否则可能导致未定义行为。
如果类中包含const成员或者引用成员,编译器无法生成默认的移动构造函数,因为这些成员无法被修改或者重新绑定,此时需要手动定义移动构造函数,或者直接使用拷贝构造函数。
另外,不要对内置类型的移动构造函数做特殊处理,因为内置类型的移动和拷贝操作是完全相同的,编译器会自动处理。
C++_Move_Constructor移动语义异常安全性右值引用资源转移修改时间:2026-06-17 08:24:36