在构造函数中抛出异常时,不会调用析构函数

时间:2012-04-02 06:32:25

标签: c++ boost scoped-ptr

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

#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 Allocated\n"; }
~MyClass() { std::cout<<"MyClass De-allocated\n"; }
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 Allocated\n"; }
    ~MyClass() { std::cout<<"MyClass De-allocated\n"; }
    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

5 个答案:

答案 0 :(得分:20)

C ++对象的生命周期仅在其构造函数成功完成后才开始 由于在构造函数调用完成之前抛出了异常,因此没有完整的对象,因此没有析构函数。

Herb Sutter explains this nicely,引用他的话:

  

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

     

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

     

在生物学方面,
   构思发生了 - 构造函数开始了 - 但是尽管做了最好的努力,然后是流产 - 构造函数从未运行到期限(ination)。

     

顺便说一句, 这就是为什么如果构造函数没有成功就永远不会调用析构函数 - 没有什么可以销毁的。 "It cannot die, for it never lived."请注意,这使短语"an object whose constructor threw an exception"真正成为矛盾。这样的事情甚至比一个前对象还要少......它从来没有生存过,从来没有,从未放过第一个。这是一个非对象。

     

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

     

或者:

     

(a)构造函数通常通过到达它的结尾或return语句返回,并且该对象存在。

     

或者:

     

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

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

是的,它会! 这是使用scoped_ptr的目的,一旦在main中抛出异常,Stack Unwinding将导致所有本地对象被释放,这意味着myinst(驻留在堆栈上)将也可以解除分配,然后调用MyClass的析构函数。

如有疑问,请参阅 Boost doccumentation

  

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

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

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

#include <boost/scoped_ptr.hpp> 
#include <iostream> 

class MyClass { 
    boost::scoped_ptr<int> ptr; 
    public: 
    MyClass() : ptr(new int) { *ptr = 0; std::cout<<"MyClass Allocated\n"; } 
    ~MyClass() { std::cout<<"MyClass De-allocated\n"; } 
    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";
    }

} 

Ref 1 C ++ 03 15.5.1 terminate()函数

  

在以下情况下,必须放弃异常处理以获得不那么微妙的错误处理技术:
  ....
   - 当异常处理机制找不到抛出异常的处理程序时(15.3),
  ....

     

在这种情况下,

     
      
  1. void terminate();
  2.         

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

答案 1 :(得分:7)

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

你只会破坏构造的东西,但你的对象永远不会完全构造。但是,您的类成员已经被构建,并且将会调用它们的析构函数。

答案 2 :(得分:0)

当从构造函数抛出异常时(开始或中途或调用结束时),确保对象不构造。
因此,它定义为不调用从未构造的对象的析构函数。

以下是Bjarne网站上的一个相关FAQ

答案 3 :(得分:0)

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

顺便说一句,如果你想显示你的调试消息 - 特别是如果你正在处理程序崩溃 - 你真的应该刷新流:即使用{{1}而不是std::endl在行尾。 (或插入'\n'

虽然仅仅使用std::flush经常有效,但有足够的情况会失败,而且如果你没有养成正确做事的习惯,那么真的会对调试感到困惑。< / p>

答案 4 :(得分:0)

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

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

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