面试题答案
一键面试1. 移动构造函数的实现
假设我们有一个类 MyClass
,内部包含动态分配的内存:
class MyClass {
private:
int* data;
int size;
public:
// 构造函数
MyClass(int s) : size(s) {
data = new int[size];
}
// 析构函数
~MyClass() {
delete[] data;
}
// 移动构造函数
MyClass(MyClass&& other) noexcept : size(other.size), data(other.data) {
other.size = 0;
other.data = nullptr;
}
};
在移动构造函数中:
- 直接接管
other
对象的资源(data
和size
)。 - 将
other
对象的资源指针设为nullptr
并将大小设为 0,这样other
对象析构时不会释放已被接管的资源。 - 使用
noexcept
说明该函数不会抛出异常,有助于编译器进行优化。
2. 移动赋值运算符的实现
class MyClass {
// 其他成员...
public:
// 移动赋值运算符
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] data;
size = other.size;
data = other.data;
other.size = 0;
other.data = nullptr;
}
return *this;
}
};
在移动赋值运算符中:
- 首先检查是否是自赋值,如果是则直接返回。
- 释放当前对象的旧资源(
delete[] data
)。 - 接管
other
对象的资源并将other
对象置为无效状态。 - 同样使用
noexcept
表明不会抛出异常。
3. 可能遇到的陷阱及避免方法
- 忘记释放旧资源:在移动赋值运算符中,如果忘记释放当前对象的旧资源(如
delete[] data
),会导致内存泄漏。要始终记得在接管新资源前释放旧资源。 - 未处理自赋值:在移动赋值运算符中,如果不检查自赋值情况,可能会导致对象自身资源被释放,进而出现未定义行为。通过
if (this != &other)
进行检查可避免这种情况。 - 未标记为
noexcept
:如果移动构造函数和移动赋值运算符没有标记为noexcept
,标准库在某些情况下(如容器的插入操作)可能不会使用移动语义,从而无法达到性能优化的目的。所以要确保标记为noexcept
,前提是函数确实不会抛出异常。 - 资源所有权混乱:确保移动操作后,源对象处于有效但可析构的状态(通常将指针设为
nullptr
等),避免在后续操作中对已被移动的对象进行非法访问。