MST

星途 面试题库

面试题:C++错误管理与异常安全保证

解释C++中异常安全的三种保证级别(基本保证、强保证、不抛出保证)。在实现一个包含动态内存分配和复杂数据结构操作的类时,如何确保该类满足强异常安全保证?请给出详细的代码示例和解释。
16.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

C++中异常安全的三种保证级别

  1. 基本保证(Basic Guarantee)
    • 解释:当异常抛出时,程序的状态仍然保持有效,没有资源泄漏,所有对象都处于一个合理的可析构状态。但是,操作可能部分完成,对象的状态可能已改变,但仍然是有效的。例如,一个容器类在插入元素时抛出异常,容器仍然可用,只是插入操作未完全成功。
  2. 强保证(Strong Guarantee)
    • 解释:如果一个操作抛出异常,程序状态不会改变,就像该操作从未发生过一样。所有对象都保持操作前的状态,没有资源泄漏。对于复杂的操作,如涉及多个步骤的事务性操作,要么全部成功,要么全部回滚。
  3. 不抛出保证(No - Throw Guarantee)
    • 解释:函数承诺永远不会抛出异常。这通常适用于一些关键的底层操作,如内存释放、析构函数等,这些操作必须保证在任何情况下都能顺利完成,否则可能导致严重的程序错误。

确保类满足强异常安全保证的示例

#include <memory>
#include <vector>
#include <iostream>

class MyComplexClass {
private:
    std::unique_ptr<int[]> data;
    int size;
public:
    MyComplexClass(int n) : size(n) {
        data = std::make_unique<int[]>(n);
        // 初始化数据,这里简单设为0
        for (int i = 0; i < n; ++i) {
            data[i] = 0;
        }
    }

    // 赋值运算符重载,实现强异常安全保证
    MyComplexClass& operator=(const MyComplexClass& other) {
        if (this == &other) {
            return *this;
        }
        // 使用临时对象来存储新数据,这样如果构造临时对象抛出异常,原对象不受影响
        std::unique_ptr<int[]> tempData = std::make_unique<int[]>(other.size);
        for (int i = 0; i < other.size; ++i) {
            tempData[i] = other.data[i];
        }
        // 构造成功后,交换数据
        data.swap(tempData);
        size = other.size;
        return *this;
    }

    // 析构函数,保证不抛出异常
    ~MyComplexClass() noexcept {
        // 自动释放unique_ptr管理的内存
    }
};

解释

  1. 构造函数:使用std::make_unique<int[]>(n)分配内存,若分配失败会抛出std::bad_alloc异常。但如果分配成功,后续初始化数据的循环不会抛出异常(这里简单初始化,实际情况可能不同),所以整体构造函数满足强异常安全保证,若构造失败,对象不会处于无效状态。
  2. 赋值运算符重载
    • 首先检查自赋值,避免不必要操作。
    • 使用临时std::unique_ptr<int[]>来存储新数据,这样在构造临时对象过程中若抛出异常(如内存分配失败),原对象datasize不受影响,满足强异常安全保证。
    • 构造临时对象成功后,通过swap操作交换数据,swap通常是异常安全的(std::unique_ptrswap是不抛出异常的)。
  3. 析构函数:声明为noexcept,保证在析构时不会抛出异常,避免异常从析构函数中抛出导致程序终止。std::unique_ptr会自动释放其管理的内存,不会有资源泄漏问题。