MST

星途 面试题库

面试题:C++构造函数与析构函数调用顺序在内存管理和异常处理中的深度分析

给定如下复杂的C++代码场景: ```cpp class Resource { public: Resource() { std::cout << "Resource constructor, allocating memory" << std::endl; data = new int[100]; } ~Resource() { std::cout << "Resource destructor, deallocating memory" << std::endl; delete[] data; } private: int* data; }; class Container { public: Container() { std::cout << "Container constructor" << std::endl; res = new Resource(); } ~Container() { std::cout << "Container destructor" << std::endl; delete res; } private: Resource* res; }; class Outer { public: Outer() { std::cout << "Outer constructor" << std::endl; cont = new Container(); } ~Outer() { std::cout << "Outer destructor" << std::endl; delete cont; } private: Container* cont; }; ``` 现在假设在 `Container` 的构造函数中,`new Resource()` 抛出了一个异常,请问整个对象层次结构中构造函数和析构函数的调用顺序是怎样的,如何通过合理设计构造函数和析构函数以及异常处理机制来保证内存的正确释放和对象状态的一致性,详细阐述思路和实现方法。
16.3万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
  1. 异常发生时构造函数和析构函数的调用顺序
    • new Resource()Container 的构造函数中抛出异常时:
      • Outer 的构造函数已经执行了 cont = new Container();,但 Container 的构造函数未完全执行完毕。
      • Outer 的析构函数会被调用,因为 Outer 的构造函数没有完全成功构造对象,根据 C++ 异常处理机制,会自动调用 Outer 的析构函数。在 Outer 的析构函数中,它会尝试删除 cont,但由于 Container 的构造函数未成功完成,cont 可能是一个未完全初始化的指针(指向一个部分构造的 Container 对象)。这里调用 delete cont; 会导致未定义行为,因为 Container 对象没有完全构造成功,res 可能是未初始化的,进而导致在 Container 的析构函数中 delete res; 也可能出错。
  2. 保证内存正确释放和对象状态一致性的思路
    • 使用智能指针
      • 智能指针可以自动管理对象的生命周期,当对象不再被使用时,智能指针会自动调用其析构函数释放内存。
      • 对于 Outer 类,将 Container* cont; 改为 std::unique_ptr<Container> cont;。这样在 Outer 的构造函数中,可以使用 cont = std::make_unique<Container>();std::unique_ptr 会在 Outer 对象析构时自动释放 Container 对象,即使 Container 的构造函数抛出异常,Outer 的析构函数也不会出现未定义行为。
      • 对于 Container 类,将 Resource* res; 改为 std::unique_ptr<Resource> res;。在 Container 的构造函数中,使用 res = std::make_unique<Resource>();。这样当 Container 对象析构时,std::unique_ptr 会自动释放 Resource 对象。
    • 异常安全设计原则
      • 构造函数应该遵循 “基本异常安全” 和 “强异常安全” 原则。基本异常安全是指如果构造函数抛出异常,对象应该处于一个有效的但可能未完全初始化的状态,并且不会泄漏任何资源。强异常安全是指如果构造函数抛出异常,程序状态应该保持不变,就像构造函数从未被调用过一样。
      • 通过使用智能指针,我们可以达到强异常安全。例如,在上述修改后的代码中,如果 Resource 的构造函数抛出异常,Container 的构造函数不会完成,std::unique_ptr<Resource> 不会持有无效的指针,Container 对象也不会被部分构造。同样,Outer 的构造函数在 Container 构造失败时,std::unique_ptr<Container> 也不会持有无效的指针,整个程序状态保持不变,没有资源泄漏。
  3. 实现方法
    • 修改后的代码如下:
#include <memory>
#include <iostream>

class Resource {
public:
    Resource() { std::cout << "Resource constructor, allocating memory" << std::endl; data = new int[100]; }
    ~Resource() { std::cout << "Resource destructor, deallocating memory" << std::endl; delete[] data; }
private:
    int* data;
};

class Container {
public:
    Container() { std::cout << "Container constructor" << std::endl; res = std::make_unique<Resource>(); }
    ~Container() { std::cout << "Container destructor" << std::endl; }
private:
    std::unique_ptr<Resource> res;
};

class Outer {
public:
    Outer() { std::cout << "Outer constructor" << std::endl; cont = std::make_unique<Container>(); }
    ~Outer() { std::cout << "Outer destructor" << std::endl; }
private:
    std::unique_ptr<Container> cont;
};

这样,无论在 ResourceContainer 还是 Outer 的构造过程中抛出异常,都能保证内存的正确释放和对象状态的一致性。