class XX
{
public:
static unsigned s_cnt;
XX()
{
++ s_cnt;
std::cout << "C XX " << s_cnt << "\n";
if ( s_cnt > 2 )
throw std::exception();
}
//private:
~XX()
{
std::cout << "~ XX\n";
}
};
unsigned XX::s_cnt = 0;
int main()
{
try
{
XX *xx = new XX[10];
} catch ( ... )
{
std::cout << "Exc\n";
}
}
输出:
C XX 1
C XX 2
C XX 3
~ XX
~ XX
Exc
但是当我删除try-catch时,我看到:
C XX 1
C XX 2
C XX 3
terminate called after throwing an instance of 'std::exception'
what(): std::exception
zsh: abort ./a.out
为什么C ++在第一种情况下调用析构函数而不在第二种情况下调用析构函数?
答案 0 :(得分:14)
当你没有捕获异常(即它成为未捕获的异常并终止你的程序)时,C ++不会保证析构函数实际被调用。
这为编译器提供了如何实现异常处理的余地。例如,GCC首先搜索处理程序。如果找不到,则立即中止,保留完整的堆栈信息以进行调试。如果它找到了一个,它实际上展开堆栈,销毁对象,直到它到达处理程序。这就是你没有看到输出的原因:程序在销毁任何对象之前就会中止。
答案 1 :(得分:3)
当您从构造函数中抛出异常时,标准会将对象视为 not construct 。你不会破坏不存在的东西,不是吗?
事实上,即使这个简单的例子也不像你建议的那样有效:
struct A {
A() {throw std::exception();}
~A() {std::cout << "A::~A()" << std::endl;}
};
int main() {
A a;
}
以未捕获的异常终止,但不打印“A :: ~A()”。
如果你考虑一下,这是给它一个语义的唯一可行方法。
例如,我们可以使用类型A作为类B的成员对象:
struct B {
B() : m_a(),m_p(new int) {}
~B() {delete m_p;}
A m_a;
int * m_p;
};
显然B::B()
会立即抛出(甚至不会初始化m_p
)。如果假设C ++标准要求在这种情况下调用B::~B()
,则析构函数无法知道m_p
是否已初始化。
换句话说,从构造函数中抛出异常意味着该对象从未存在且其生命周期从未开始。这个GotW非常清楚。
奖励:如果在B
的定义中我们交换m_a
和m_p
的顺序,会发生什么?
然后你有内存泄漏。这就是为什么在成员对象初始化期间存在捕获异常的特定语法的原因。