如果构造函数抛出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的顺序是:
- m_aObject将会调用它的构造函数。
- m_aData将会有它的构造函数。
- 类构造函数被调用
- 在类的构造函数中,m_pThing将会调用新的构造函数。
- 在类的构造函数中,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会被调用,这是使用智能指针对象的主要原因 – 它们是在这种情况下防止内存泄漏的好方法。