Вызывается ли деструктор класса C ++, который выбрасывает исключение? - PullRequest
6 голосов
/ 28 июня 2010

Предположим, у меня есть такой класс:

#include <iostream>

using namespace std;

class Boda {
    private:
        char *ptr;

    public:
        Boda() {
            ptr = new char [20];
        }
        ~Boda() {
            cout << "calling ~Boda\n";

            delete [] ptr;
        }

        void ouch() {
            throw 99;
        }
};

void bad() {
    Boda b;
    b.ouch();
}

int main() {
    bad();
}

Кажется, что деструктор ~Boda никогда не вызывается, поэтому ресурс ptr никогда не освобождается.

Вотвывод программы:

terminate called after throwing an instance of 'int'
Aborted

Так что, похоже, ответ на мой вопрос No.

Но я думал, что стек был размотан, когда возникло исключение?Почему в моем примере объект Boda b не был уничтожен?

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

Кроме того, это так называемое RAII?

Спасибо, Бода Кидо.

Ответы [ 3 ]

8 голосов
/ 28 июня 2010

Если исключение нигде не перехватывается, то среда выполнения C ++ может сразу перейти к завершению программы без разматывания стека или вызова деструкторов.

Однако, если вы добавите блок try-catch вокруг вызова к bad(), вы увидите деструктор для вызываемого объекта Boda:

int main() {
    try {
      bad();
    } catch(...) {  // Catch any exception, forcing stack unwinding always
      return -1;
    }
}

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

Такое поведение в угловом случае обычно не является проблемой в отношении RAII, поскольку обычно основная причина, по которой вы хотите, чтобы деструкторы запускались, - это освобождение памяти, и вся память возвращается ОС, когда ваша программа все равно завершается. Однако, если ваши деструкторы делают что-то более сложное, например, удаляют файл блокировки на диске или что-то в этом роде, где будет иметь значение, будет ли программа вызывать деструкторы или нет при сбое, вы можете обернуть main в try-catch блок, который перехватывает все (только для выхода при исключении в любом случае), просто чтобы гарантировать, что стек всегда раскручивается перед завершением.

2 голосов
/ 28 июня 2010

Деструктор не будет запущен, если в конструкторе возникнет исключение.

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

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

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

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

В любом другом методе используйте исключения по своему усмотрению, но не забывайте где-нибудь их обрабатывать. Необработанное исключение может быть хуже, чем не исключение вообще. Я знаю о некоторых программах, которые не обрабатывают исключения для некоторых незначительных ошибок ... и аварийно завершают работу при ошибках, которые должны выдавать только предупреждение.

1 голос
/ 28 июня 2010

Попробуйте очистить поток - вы увидите, что деструктор действительно называется:

cout << "calling ~Boda" << endl;

Это буферизация ввода / вывода, которая задерживает распечатку до точки, в которую прерывание программы прерывается до фактического вывода.

Edit:

Вышеуказанное относится к обработанным исключениям . С необработанными исключениями стандарт не указывает, разматывается ли стек или нет. См. Также этот вопрос SO .

...