面试题答案
一键面试使用 std::move
优化过程
- 首先,确保
MyClass
类实现了移动构造函数和移动赋值运算符。移动构造函数可以高效地将源对象的动态分配资源“窃取”过来,而不是进行深拷贝。class MyClass { private: int* data; public: MyClass() : data(new int(0)) {} MyClass(const MyClass& other) : data(new int(*other.data)) {} MyClass(MyClass&& other) noexcept : data(other.data) { other.data = nullptr; } MyClass& operator=(const MyClass& other) { if (this != &other) { delete data; data = new int(*other.data); } return *this; } MyClass& operator=(MyClass&& other) noexcept { if (this != &other) { delete data; data = other.data; other.data = nullptr; } return *this; } ~MyClass() { delete data; } };
- 当将
MyClass
对象放入std::vector
时,使用std::move
来避免不必要的拷贝。例如:
这样,#include <vector> #include <iostream> int main() { std::vector<MyClass> vec; MyClass obj; vec.push_back(std::move(obj)); return 0; }
push_back
操作会调用MyClass
的移动构造函数,而不是拷贝构造函数,从而提高性能。
可能遇到的陷阱及避免方法
- 悬空指针问题:如果在移动后继续使用源对象,会导致悬空指针。例如:
避免方法:移动后不要再访问源对象的已移动资源。在移动构造函数和移动赋值运算符中,将源对象的指针成员设为MyClass obj; MyClass obj2 = std::move(obj); std::cout << *obj.data << std::endl; // 未定义行为,obj.data 已被移动走变为悬空指针
nullptr
,这样可以防止意外访问。 - 异常安全:如果移动构造函数或移动赋值运算符抛出异常,可能会导致资源泄漏或对象处于不一致状态。例如,如果移动构造函数在“窃取”资源后抛出异常,源对象的资源已被修改,而新对象构造未完成。
避免方法:确保移动构造函数和移动赋值运算符是
noexcept
的,这样在移动操作过程中不会抛出异常。如果确实无法避免异常,需要在移动操作中处理好异常情况,保证资源的正确释放和对象状态的一致性。 - 移动语义误用:如果错误地在不支持移动语义的类型上使用
std::move
,会导致不必要的性能损失。例如对std::string
,如果其大小小于内部缓冲区(SSO 机制),拷贝和移动效率相近,使用std::move
可能没有优化效果甚至有轻微性能损失。 避免方法:了解类型的特性,确保使用std::move
确实能带来性能提升。对于标准库类型,查阅文档了解其在不同情况下的性能表现。