MST

星途 面试题库

面试题:C++ 中 std::move 在容器操作中的应用及陷阱

假设你有一个自定义类 `MyClass`,包含动态分配的内存资源。你要将 `MyClass` 对象放入 `std::vector` 中,描述如何使用 std::move 优化这个过程,并且说明在这个过程中可能会遇到哪些陷阱,如何避免这些陷阱。
48.1万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

使用 std::move 优化过程

  1. 首先,确保 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; }
    };
    
  2. 当将 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 的移动构造函数,而不是拷贝构造函数,从而提高性能。

可能遇到的陷阱及避免方法

  1. 悬空指针问题:如果在移动后继续使用源对象,会导致悬空指针。例如:
    MyClass obj;
    MyClass obj2 = std::move(obj);
    std::cout << *obj.data << std::endl; // 未定义行为,obj.data 已被移动走变为悬空指针
    
    避免方法:移动后不要再访问源对象的已移动资源。在移动构造函数和移动赋值运算符中,将源对象的指针成员设为 nullptr,这样可以防止意外访问。
  2. 异常安全:如果移动构造函数或移动赋值运算符抛出异常,可能会导致资源泄漏或对象处于不一致状态。例如,如果移动构造函数在“窃取”资源后抛出异常,源对象的资源已被修改,而新对象构造未完成。 避免方法:确保移动构造函数和移动赋值运算符是 noexcept 的,这样在移动操作过程中不会抛出异常。如果确实无法避免异常,需要在移动操作中处理好异常情况,保证资源的正确释放和对象状态的一致性。
  3. 移动语义误用:如果错误地在不支持移动语义的类型上使用 std::move,会导致不必要的性能损失。例如对 std::string,如果其大小小于内部缓冲区(SSO 机制),拷贝和移动效率相近,使用 std::move 可能没有优化效果甚至有轻微性能损失。 避免方法:了解类型的特性,确保使用 std::move 确实能带来性能提升。对于标准库类型,查阅文档了解其在不同情况下的性能表现。