如果构造函数抛出exception,析构函数是否被调用?

寻找C#和C ++的答案。 (在C#中,用'finalizer'replace'destructor')

序言:香草萨特在这个问题上有一个伟大的文章:

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

C ++:是和否

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

作为一个总结,对象的每个内部部分(即成员对象)将按照它们的构造的相反顺序来调用它们的析构函数。 在构造函数内部构build的每个东西都不会有其析构函数被调用,除非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() ; } 

创build的顺序是:

  1. m_aObject将会调用它的构造函数。
  2. m_aData将会有它的构造函数。
  3. 类构造函数被调用
  4. 在类的构造函数中,m_pThing将会调用新的构造函数。
  5. 在类的构造函数中,m_pGizmo将会调用它的新构造函数。

假设我们正在使用下面的代码:

 Class pClass = new Class() ; 

一些可能的情况:

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

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

  • 如果m_pThing在施工时抛出,则“new thing”分配的内存将被释放。 然后m_aData,然后m_aObject将会调用它的析构函数。 然后,释放由新类分配的内存。

  • 如果m_pGizmo在施工时抛出,“新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()) ; } 

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

这样,无论构造函数在哪里抛出,都不会泄漏。

它适用于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(); } } 

这打印“定稿”

仍在构build的类的析构函数不会被调用,因为对象从来没有被完全构造过。

但是,它的基类(如果有的话)的析构函数被调用,因为该对象被构造为一个基类对象。

而且,任何成员variables都会有他们的析构函数(正如其他人所指出的那样)。

注意:这适用于C ++

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

但是,除非构造其中的一个exception,否则调用对象上任何成员数据的析构函数。

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

如果构造函数没有完成执行,对象不存在,所以没有什么可以破坏的。 这是在C ++中,我不知道关于C#。

C ++ –

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

顺便说一句 – 你真正想要的是所谓的“堆栈解绕”

不要在构造函数中造成exception。

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

对于C ++来说,这是在前面的问题中解决的: 下面的代码是否会导致c ++中的内存泄漏

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