Вызывается ли деструктор, если конструктор выдает исключение? - PullRequest
41 голосов
/ 09 октября 2008

Ищем ответ на C # и C ++. (в C # заменить «деструктор» на «финализатор»)

Ответы [ 8 ]

51 голосов
/ 09 октября 2008

Преамбула: у Херба Саттера есть отличная статья на эту тему:

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. Внутри конструктора класса m_pThing будет иметь свой новый, а затем вызываемый конструктор.
  5. Внутри конструктора класса m_pGizmo будет иметь свой новый и затем вызываемый конструктор.

Допустим, мы используем следующий код:

Class pClass = new Class() ;

Некоторые возможные случаи:

  • Если m_aData сгенерирует конструкцию, то у m_aObject будет вызван деструктор. Затем память, выделенная «новым классом», освобождается.

  • Если m_pThing выбросит новое Thing (из памяти), m_aData, а затем m_aObject вызовут свои деструкторы. Затем память, выделенная новым классом, освобождается.

  • Если m_pThing сгенерирует конструкцию, память, выделенная "новой вещью", будет освобождена. Затем 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()) ;
}

если вы хотите / должны создать эти объекты внутри конструктора.

Таким образом, независимо от того, куда бросает конструктор, ничто не будет пропущено.

49 голосов
/ 09 октября 2008

Это делается для 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();
    }
}

Это печатает "Завершено"

10 голосов
/ 10 октября 2008

Деструктор класса, который все еще создается, не вызывается, потому что объект никогда не создавался полностью.

Однако деструктор его базового класса (если таковой имеется) вызывается, потому что объект был сконструирован настолько, насколько это был объект базового класса.

Более того, любые переменные-члены тоже будут вызывать свои деструкторы (как уже отмечали другие).

Примечание: это относится к C ++

2 голосов
/ 09 октября 2008

В C ++ ответ - нет - деструктор объекта называется , а не .

Однако деструкторы любых данных-членов объекта будут вызываться , если только исключение не было выдано при создании одного из им .

Данные-члены в C ++ инициализируются (т.е. создаются) в том же порядке, в котором они были объявлены, поэтому, когда конструктор выбрасывает, все данные-члены, которые были инициализированы - либо явно в списке инициализации элементов (MIL), либо иным образом, - будут быть снесенным снова в обратном порядке.

1 голос
/ 09 октября 2008

Если конструктор не завершает выполнение, объект не существует, поэтому уничтожать нечего. Это на C ++, я понятия не имею о C #.

0 голосов
/ 10 октября 2008

Для C ++ это было решено в предыдущем вопросе: Будет ли приведенный ниже код причиной утечки памяти в c ++

Так как в C ++, когда в конструкторе генерируется исключение, деструктор не вызывается, но вызывается dtors для членов объекта (которые были сконструированы), это основная причина для использования объектов интеллектуальных указателей над необработанными указателями - это хороший способ предотвратить утечку памяти в такой ситуации.

0 голосов
/ 09 октября 2008

Не делайте вещей, которые вызывают исключения в конструкторе.

Вызовите Initialize () после конструктора, который может выдавать исключения.

0 голосов
/ 09 октября 2008

C ++ -

Нет. Деструктор не вызывается для частично построенных объектов. Предупреждение: деструктор будет вызываться для его объектов-членов, которые полностью построены. (Включает в себя автоматические объекты и нативные типы)

Кстати - то, что вы действительно ищете, называется "разматывание стека"

...