#include <iostream>
#include <memory>
class MyClass {
private:
int* data;
public:
MyClass() : data(new int(42)) {
std::cout << "Constructor: Allocated memory at " << data << std::endl;
}
~MyClass() {
if (data) {
std::cout << "Destructor: Deallocating memory at " << data << std::endl;
delete data;
}
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "Move Constructor: Transferred ownership of memory at " << data << std::endl;
}
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data;
data = other.data;
other.data = nullptr;
std::cout << "Move Assignment: Transferred ownership of memory at " << data << std::endl;
}
return *this;
}
};
int main() {
MyClass obj1;
MyClass obj2 = std::move(obj1);
return 0;
}
原理解释
- 移动构造函数:当使用
std::move
转移对象所有权时,移动构造函数会被调用。在上述代码中,MyClass(MyClass&& other) noexcept
将other
对象中的data
指针直接赋值给新对象,然后将other.data
设为nullptr
。这样新对象就拥有了原对象动态分配的内存,而原对象不再拥有这块内存,避免了重复释放。
- 移动赋值运算符:如果是使用移动赋值(
obj2 = std::move(obj1)
),移动赋值运算符MyClass& operator=(MyClass&& other) noexcept
会被调用。它先释放自身已有的内存(如果有),然后将other
对象的data
指针赋值过来,并将other.data
设为nullptr
。
- 析构函数:在对象生命周期结束时,析构函数会被调用。由于移动操作后,只有新对象拥有动态分配内存的指针,所以只有新对象的析构函数会释放这块内存,而原对象(已被移动)的析构函数不会对已转移所有权的内存进行重复释放,因为其指针已被设为
nullptr
。这确保了动态分配内存的正确释放,避免了内存泄漏和悬空指针问题。