面试题答案
一键面试- 异常发生时构造函数和析构函数的调用顺序
- 当
new Resource()
在Container
的构造函数中抛出异常时:Outer
的构造函数已经执行了cont = new Container();
,但Container
的构造函数未完全执行完毕。Outer
的析构函数会被调用,因为Outer
的构造函数没有完全成功构造对象,根据 C++ 异常处理机制,会自动调用Outer
的析构函数。在Outer
的析构函数中,它会尝试删除cont
,但由于Container
的构造函数未成功完成,cont
可能是一个未完全初始化的指针(指向一个部分构造的Container
对象)。这里调用delete cont;
会导致未定义行为,因为Container
对象没有完全构造成功,res
可能是未初始化的,进而导致在Container
的析构函数中delete res;
也可能出错。
- 当
- 保证内存正确释放和对象状态一致性的思路
- 使用智能指针:
- 智能指针可以自动管理对象的生命周期,当对象不再被使用时,智能指针会自动调用其析构函数释放内存。
- 对于
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>
也不会持有无效的指针,整个程序状态保持不变,没有资源泄漏。
- 使用智能指针:
- 实现方法
- 修改后的代码如下:
#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;
};
这样,无论在 Resource
、Container
还是 Outer
的构造过程中抛出异常,都能保证内存的正确释放和对象状态的一致性。