MST

星途 面试题库

面试题:C++移动构造函数与异常安全

编写一个包含移动构造函数的`ResourceHolder`类,该类持有一个动态分配的资源(例如`int*`)。在移动构造函数中实现异常安全机制,即在移动过程中如果发生异常,确保源对象和目标对象的状态都是合理的,并且资源不会泄露。同时解释为什么要这样设计以及可能会遇到的陷阱。
44.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
#include <iostream>
#include <memory>

class ResourceHolder {
private:
    int* resource;

public:
    ResourceHolder() : resource(nullptr) {}

    ResourceHolder(int value) : resource(new int(value)) {}

    // 移动构造函数
    ResourceHolder(ResourceHolder&& other) noexcept {
        resource = other.resource;
        other.resource = nullptr;
    }

    // 析构函数
    ~ResourceHolder() {
        delete resource;
    }

    // 复制构造函数
    ResourceHolder(const ResourceHolder& other) {
        if (other.resource) {
            resource = new int(*other.resource);
        } else {
            resource = nullptr;
        }
    }

    // 赋值运算符重载
    ResourceHolder& operator=(const ResourceHolder& other) {
        if (this != &other) {
            delete resource;
            if (other.resource) {
                resource = new int(*other.resource);
            } else {
                resource = nullptr;
            }
        }
        return *this;
    }

    // 移动赋值运算符重载
    ResourceHolder& operator=(ResourceHolder&& other) noexcept {
        if (this != &other) {
            delete resource;
            resource = other.resource;
            other.resource = nullptr;
        }
        return *this;
    }
};

设计原因

  1. 资源转移:移动构造函数的主要目的是高效地将资源从一个对象转移到另一个对象,而不是进行资源的复制。在上述代码中,ResourceHolder(ResourceHolder&& other) noexcept函数将other对象的resource指针直接赋值给当前对象,并将other.resource设为nullptr,从而完成资源的转移,避免了不必要的内存分配和复制操作,提高了性能。
  2. 异常安全:通过将noexcept关键字添加到移动构造函数和移动赋值运算符重载函数中,表明这些函数不会抛出异常。这使得在移动过程中,如果发生异常,源对象和目标对象的状态都是合理的。因为移动操作不涉及新的内存分配(除了可能在复制构造函数或赋值运算符重载中),所以在移动过程中发生异常的可能性较低。并且,即使发生异常,由于资源已经成功转移,源对象的状态仍然是安全的(resourcenullptr),不会导致资源泄露。

可能遇到的陷阱

  1. 忘记noexcept声明:如果移动构造函数和移动赋值运算符重载函数没有声明为noexcept,一些标准库算法(如std::vectorpush_back等)在进行对象移动时可能会采用更保守的策略(如进行复制而不是移动),从而降低性能。
  2. 源对象状态未重置:在移动构造函数和移动赋值运算符重载函数中,如果没有将源对象的资源指针设为nullptr,源对象在析构时会再次释放已经转移的资源,导致双重释放错误,进而引发未定义行为。
  3. 异常处理不当:虽然移动操作一般很少抛出异常,但如果在移动构造函数或移动赋值运算符重载函数中进行了可能抛出异常的操作(如分配新的内存等),而没有正确处理异常,可能会导致资源泄露或对象处于不一致的状态。在上述代码中,移动操作只是简单的指针赋值,不涉及可能抛出异常的操作,从而保证了异常安全。