如果构造函数抛出异常,是否会调用析构函数?

时间:2008-10-09 19:01:06

标签: c# c++ destructor finalizer

寻找C#和C ++的答案。 (在C#中,将'析构者'替换为'终结者')

8 个答案:

答案 0 :(得分:51)

序言:Herb Sutter有一篇关于这个主题的精彩文章:

http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-c-c-and-java/

C ++:是和否

如果构造函数抛出(对象“从未存在”),则不会调用对象析构函数,但可以调用其内部对象的析构函数。

作为总结,对象的每个内部部分(即成员对象)将按其构造的相反顺序调用它们的析构函数。除非以某种方式使用RAII,否则构造函数内部构建的每个东西都不会调用析构函数。

例如:

struct Class
{
   Class() ;
   ~Class() ;

   Thing *    m_pThing ;
   Object     m_aObject ;
   Gizmo *    m_pGizmo ;
   Data       m_aData ;
}

Class::Class()
{
   this->m_pThing = new Thing() ;
   this->m_pGizmo = new Gizmo() ;
}

创作顺序为:

  1. m_aObject将使用其构造函数。
  2. m_aData将其构造函数调用为。
  3. 类构造函数被称为
  4. 在Inside类构造函数中,m_pThing将使用其新的构造函数。
  5. 在Inside类构造函数中,m_pGizmo将使用其新的构造函数。
  6. 假设我们使用以下代码:

    Class pClass = new Class() ;
    

    一些可能的情况:

    • 如果m_aData在构造时抛出,m_aObject将调用其析构函数。然后,取消分配由“new Class”分配的内存。

    • 如果m_pThing抛出新的Thing(内存不足),m_aData,然后m_aObject将调用它们的析构函数。然后,取消分配由新类分配的内存。

    • 如果m_pThing在构造时抛出,“new Thing”分配的内存将被释放。然后m_aData,然后m_aObject将调用它们的析构函数。然后,取消分配由新类分配的内存。

    • m_pGizmo应该在构造时抛出,“new Gizmo”分配的内存将被释放。然后m_aData,然后m_aObject将调用它们的析构函数。然后,释放由新类分配的内存。 请注意,m_pThing已泄露

    如果要提供基本异常保证,即使在构造函数中也不得泄漏。因此,你必须这样写(使用STL,甚至是Boost):

    struct Class
    {
       Class() ;
       ~Class() ;
    
       std::auto_ptr<Thing>   m_pThing ;
       Object                 m_aObject ;
       std::auto_ptr<Gizmo>   m_pGizmo ;
       Data                   m_aData ;
    }
    
    Class::Class()
       : m_pThing(new Thing())
       , m_pGizmo(new Gizmo())
    {
    }
    

    甚至:

    Class::Class()
    {
       this->m_pThing.reset(new Thing()) ;
       this->m_pGizmo.reset(new Gizmo()) ;
    }
    

    如果你想/需要在构造函数中创建这些对象。

    这样,无论构造函数在哪里抛出,都不会泄露任何内容。

答案 1 :(得分:49)

它适用于C#(请参阅下面的代码),但不适用于C ++。

using System;

class Test
{
    Test()
    {
        throw new Exception();
    }

    ~Test()
    {
        Console.WriteLine("Finalized");
    }

    static void Main()
    {
        try
        {
            new Test();
        }
        catch {}
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

这会打印“已完成”

答案 2 :(得分:10)

仍未构造的类的析构函数未被调用,因为该对象从未完全构造。

然而,调用其基类(如果有的话)的析构函数,因为该对象被构造为基类对象。

此外,任何成员变量也会调用它们的析构函数(正如其他人所指出的那样)。

注意:这适用于C ++

答案 3 :(得分:2)

在C ++中,答案是否定的 - 对象的析构函数被调用。

但是,除非在构造它们之一时抛出异常,否则将调用对象上的任何成员数据的析构函数

C ++中的成员数据按照声明的顺序进行初始化(即构造),因此当构造函数抛出时,所有已初始化的成员数据 - 显式地在成员初始化列表(MIL)中或其他 - 将以相反的顺序再次被拆除。

答案 4 :(得分:1)

如果构造函数没有完成执行,则该对象不存在,因此没有任何东西可以破坏。这是用C ++编写的,我不知道C#。

答案 5 :(得分:0)

C ++ -

不。不为部分构造的对象调用析构函数。警告:析构函数将被调用其完全构造的成员对象。 (包括自动对象和本机类型)

BTW - 你真正想要的是“堆栈展开”

答案 6 :(得分:0)

不要在构造函数中执行导致异常的事情。

在可以抛出异常的构造函数之后调用Initialize()。

答案 7 :(得分:0)

对于C ++,这在前一个问题中得到解决:Will the below code cause memory leak in c++

因为在C ++中,当构造函数中抛出异常时,析构函数不会被调用,但是对象的成员(已构造的)的dtors会被调用,这是使用智能指针对象而不是原始指针的主要原因 - 在这种情况下,它们是防止内存泄漏的好方法。