在构造函数中引发异常时不调用析构函数



为什么在此代码中不调用析构函数?

#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
boost::scoped_ptr<int> ptr;
public:
MyClass() : ptr(new int) { *ptr = 0; throw; std::cout<<"MyClass Allocatedn"; }
~MyClass() { std::cout<<"MyClass De-allocatedn"; }
int increment() { return ++*ptr; }
};
int main()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass);
    std::cout << myinst->increment() << 'n';
    std::cout << myinst->increment() << 'n';
}

编辑

从答案来看,了解当构造函数中发生异常时,不会调用析构函数。但是,如果异常发生在main((中,即在MyClass对象完全实例化之后,MyClass析构函数会被调用吗?如果不是,那么为什么它是一个智能指针?

添加代码

#include <boost/scoped_ptr.hpp>
#include <iostream>
class MyClass {
    boost::scoped_ptr<int> ptr;
    public:
    MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocatedn"; }
    ~MyClass() { std::cout<<"MyClass De-allocatedn"; }
    int increment() { return ++*ptr; }
};
int main()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass);
    throw 3;
    std::cout << myinst->increment() << 'n';
    std::cout << myinst->increment() << 'n';
}

输出:

MyClass Allocated
terminate called after throwing an instance of 'int'
Aborted

C++对象的生存期仅在其构造函数成功完成后才开始。
由于异常是在构造函数调用完成之前引发的,因此您没有完整的对象,因此没有析构函数。

赫伯·萨特(Herb Sutter(很好地解释了这一点,引用他的话:

问: 从构造函数发出异常是什么意思?

答:这意味着建造失败了,物体从未存在过,它的生命周期从未开始。事实上,报告构造失败的唯一方法 - 即无法正确构建给定类型的功能对象 - 是抛出异常。(是的,有一个现在已经过时的编程约定说,"如果你遇到麻烦,只需将状态标志设置为'坏',并让调用者通过 IsOK(( 函数检查它。我现在将对此发表评论。

在生物学方面,
受孕发生了——构造函数开始了——但尽管尽了最大的努力,它还是流产了——构造函数从未跑到术语(ination(。

顺便说一下,这就是为什么如果构造函数没有成功,则永远不会调用析构函数 - 没有什么可破坏的。 "It cannot die, for it never lived." 请注意,这使得这句话"an object whose constructor threw an exception"实际上是矛盾的。这样的东西甚至比前对象还要少...它从未活过,从来没有,从来没有呼吸过它的第一口气。它是一个非对象。

我们可以将构造函数模型C++总结如下:

也:

(a( 构造函数通过到达其结束或返回语句来正常返回,并且对象存在。

或:

(b( 构造函数通过发出异常退出,并且该对象不仅现在不存在,而且从未作为对象存在。

编辑 1:
但是,如果在main()中发生异常,即在MyClass对象完全实例化之后,是否会调用MyClass析构函数?

是的,会的!
这就是使用scoped_ptr的目的,一旦在main抛出异常,堆栈展开会导致所有本地对象被解除分配,这意味着myinst(驻留在堆栈上(也会被解除分配,进而调用MyClass的析构函数。

如有疑问,请参阅提升文档

scoped_ptr类模板存储指向动态分配对象的指针。(动态分配的对象使用 C++ 新表达式进行分配。所指向的对象保证被删除,无论是在销毁scoped_ptr时,还是通过明确的reset

编辑2:
为什么您编辑的程序会崩溃?
您的程序显示崩溃,因为,您抛出异常,但从未捕获它。当这种情况发生时,将调用一个名为 terminate() 的特殊函数,其默认行为是调用 abort() 。在此特定场景中调用Ref 1 之前,堆栈是否展开是实现定义的行为 terminate()。似乎你的实现没有,你也不应该依赖这种行为。

您可以按如下方式修改程序以处理异常,并且应该获得预期的行为:

#include <boost/scoped_ptr.hpp> 
#include <iostream> 
class MyClass { 
    boost::scoped_ptr<int> ptr; 
    public: 
    MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocatedn"; } 
    ~MyClass() { std::cout<<"MyClass De-allocatedn"; } 
    int increment() { return ++*ptr; } 
}; 
void doSomething()
{
    boost::scoped_ptr<MyClass> myinst(new MyClass); 
    throw 3; 
} 
int main() 
{
    try 
    {
        doSomething();    
    }
    catch(int &obj)
    {
        std::cout<<"Exception Handled";
    }
} 

Ref1C++03 15.5.1 终止(( 函数

在以下情况下,必须放弃异常处理,转而使用不太微妙的错误处理技术:
....
— 当异常处理机制找不到抛出的异常的处理程序 (15.3( 时,
....

在这种情况下,

  1. 无效终止((;

称为 (18.6.3(。在找不到匹配处理程序的情况下,在调用堆栈之前是否展开堆栈terminate()由实现定义。在所有其他情况下,在调用堆栈之前不得展开堆栈terminate()。不允许实现基于展开过程最终将导致调用terminate()而过早完成堆栈展开。

因为在这种情况下调用析构函数没有意义。

你只破坏被构造的东西,但你的对象从未完全构造过。但是,您的类成员已被构造,并且将调用其析构函数。

如果构造函数引发异常,则不会调用该类的析构函数,因为该对象未完全构造。

请参阅此链接,了解如何在这种情况下管理资源:

http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10

当从

构造函数中抛出异常时(调用的开始或中途或结束时(,则可以确保对象未构造。
因此,不调用从未构造过的对象的析构函数是很好的定义。

这是Bjarne网站上的一个相关常见问题解答。

从未调用过 MyClass 的析构函数,因为从未构造过 MyClass 类型的对象。由于抛出异常,每次构造一个的尝试都中止了。

顺便说一句,如果您希望显示调试消息 - 特别是如果您正在处理程序崩溃 - 您确实应该刷新流:即使用std::endl而不是行尾的'n'。(或插入std::flush(

虽然仅仅使用'n'通常有效,但在足够多的情况下它会失败,如果你不养成正确做事的习惯,调试真的非常非常令人困惑。

最新更新