49

寻找 C# 和 C++ 的答案。(在 C# 中,将“析构函数”替换为“终结器”)

4

8 回答 8

57

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

这将打印“Finalized”

于 2008-10-09T19:07:53.407 回答
55

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

http://herbsutter.wordpress.com/2008/07/25/constructor-exceptions-in-cc-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. 在 Class 构造函数中,m_pThing 将调用其 new 构造函数。
  5. 在 Class 构造函数中,m_pGizmo 将调用其 new 构造函数。

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

Class pClass = new Class() ;

一些可能的情况:

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

  • 如果 m_pThing 抛出新事物(内存不足),m_aData,然后 m_aObject 将调用它们的析构函数。然后,由 new Class 分配的内存被释放。

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

  • 如果 m_pGizmo 在构建时抛出,“new Gizmo”分配的内存将被释放。然后是 m_aData,然后是 m_aObject 将调用它们的析构函数。然后,由 new Class 分配的内存被释放。请注意,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()) ;
}

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

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

于 2008-10-09T19:43:40.573 回答
11

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

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

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

注意:这适用于 C++

于 2008-10-09T20:06:34.847 回答
2

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

但是,将调用对象上任何成员数据的析构函数,除非在构造其中一个时抛出异常

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

于 2008-10-09T19:14:03.800 回答
1

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

于 2008-10-09T19:08:19.083 回答
1

对于 C++,这在上一个问题中得到了解决: 以下代码是否会导致 c++ 中的内存泄漏

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

于 2008-10-09T21:26:47.760 回答
0

C++ -

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

顺便说一句 - 你真正要找的是“堆栈展开”

于 2008-10-09T19:13:48.370 回答
0

不要做导致构造函数异常的事情。

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

于 2008-10-09T19:16:20.143 回答