Исключение в конструкторе производного класса - PullRequest
1 голос
/ 08 сентября 2011

У меня возникли проблемы с обработкой исключений конструктора в производных классах. Когда конструктор производного класса выдает ошибку, но родительский класс выделил некоторые объекты. Будет ли вызываться деструктор родительского класса?

Пример:

class A
{
  A() { /* Allocate some stuff */ };

  virtual ~A() { /* Deallocate stuff */ };
};

class B : public A
{
  B() 
  { 
    /* Do some allocations */
    ...

    /* Something bad happened here */
    if (somethingBadHappened) 
    {
      /* Deallocate B Stuff */
      ...

      /* Throws the error */
      throw Error; /* Will A destructor be called? I know B destructor won't */
    };

  };

  ~B() { /* Deallocate B Stuff */ };
}

И мне было интересно, если это хорошая идея сделать следующее:

B()
{ 
  /* Do some allocations */
  ...

  /* Something bad happened here */
  if (somethingBadHappened) 
  {
    /* Deallocate B Stuff */
    this->~B();

    /* Throws the error */
    throw Error; /* Will A destructor be called? I know B destructor won't */
  };
};

Если нет, то каков приличный способ делать такие вещи?

Ответы [ 4 ]

6 голосов
/ 08 сентября 2011

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

Попробуйте это:

#include <iostream>

class A
{
public:
  A() { std::cout << "A::A()\n";}
  ~A() {std::cout << "A::~A()\n";}
};

class B : public A
{
public:
   B()
   {
      std::cout << "B::B()\n";
      throw 'c';
   }

   // note: a popular point of confusion -- 
   //   in this example, this object's destructor
   //   WILL NOT BE CALLED!
   ~B()
   {
      std::cout << "B::~B()\n";
   }
};


int main()
{
   try
   {
      B b;
   }

   catch(...)
   {
      std::cout << "Fin\n";
   }
   return 0;
}

Вывод должен быть: (примечание B::~B() не вызывается)

A::A()
B::B()
A::~A()
Fin

Вызов деструктора вручную, как вы показали в своем вопросе, будет безопасным, если вы не пытаетесь освободить ресурсы, которые вы еще не выделили. Лучше обернуть эти ресурсы в некоторый тип контейнера RAII (std::auto_ptr, boost::shared_ptr и т. Д.), Чтобы избежать необходимости вызова деструктора.

Mooing Duck предоставил очень хорошую иллюстрацию того, как работает раскрутка стека, когда в конструкторе выдается исключение:

Stack Unwinding during constructor exception

0 голосов
/ 08 сентября 2011

Лучшая идея - перехватить исключения внутри конструкции, а затем перевести объект в состояние, при котором вещи будут вызывать ошибки (например, объект для чтения файла, открытие файла в конструкторе не удастся, тогда чтение не будет работать).*

Просто держите объект согласованным.

0 голосов
/ 08 сентября 2011

Ваша неудачная попытка написать чистый конструктор B::B() во второй части вопроса подчеркивает неловкость дизайна, который принимает слишком большую ответственность в одном классе.Если вы используете только компоненты с одной ответственностью, вы часто можете вообще не писать никаких явных проверок ошибок и позволить механизму обработки исключений выполнять свою работу рекурсивно.

Учтите это:

Как видите, написание правильного конструктора для класса, у которого есть даже три разные обязанности, - это кошмар обслуживания.

Правильное решение состоит в том, чтобы каждый ресурс принадлежал классу-обёртке, единственной ответственностью которого являетсявладеть этим ресурсом, и очистка происходит автоматически даже перед самым исключительным исключением.

Также обратите внимание, что вы должны быть крайне осторожны при вызове функций-членов из любого конструктора,Время жизни объекта не начинается до тех пор, пока не закончится конструктор, поэтому, пока вы находитесь в конструкторе, вы работаете с «строящимся объектом» - что-то вроде операции на открытом сердце ... над собой.В частности, вы не должны вызывать деструктор, потому что вам разрешено уничтожать только законченные объекты.

0 голосов
/ 08 сентября 2011

Не думал об этом до конца, но, возможно, стоит подумать о создании объекта в блоке try / catch. Если конструктор выдает исключение, delete объект, если он был создан с использованием new.

try
{
    B* b = new B();
}
catch
{
    delete b;
    //log error
}

Если вы не используете new для выделения памяти для b, вам не нужно вызывать delete в блоке catch.

Убедитесь, что ваш деструктор B не вызывает delete для объектов, которые никогда не создавались. Я бы порекомендовал установить все члены, которые являются указателями на объекты, равные 0, в вашем конструкторе, прежде чем делать что-либо, что может вызвать исключение. Таким образом, если деструктор вызван, delete его безопасно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...