Что произойдет, если delete [] p не получится? - PullRequest
20 голосов
/ 28 июня 2011

Предположим, у меня есть указатель на динамически распределенный массив из 10 элементов:

T* p = new T[10];

Позже я хочу освободить этот массив:

delete[] p;

Что произойдет, если один изT деструкторы выдает исключение?Остальные элементы все еще разрушаются?Будет ли освобождена память?Будет ли распространяться исключение, или выполнение программы будет прекращено?

Аналогично, что происходит, когда уничтожается std::vector<T> и один из T бросает деструкторы?

Ответы [ 6 ]

6 голосов
/ 28 июня 2011

Я не вижу его явно в стандарте:

Просто они будут вызываться в обратном порядке создания

5.3.5 Удалить [expr.delete]

6 Если значение операнда выражения удаления не является нулевым значением указателя, выражение удаления вызовет деструктор (если он есть) для объекта или элементов удаляемого массива. В случае массива элементы будут уничтожены в порядке убывания адреса (то есть в обратном порядке завершения их конструктора; см. 12.6.2).

И что освобождение памяти будет выполнено, даже если сгенерировано исключение:

7 Если значение операнда выражения удаления не является нулевым значением указателя, выражение удаления вызовет функцию освобождения (3.7.4.2). В противном случае не определено, будет ли вызвана функция освобождения. [ Примечание: функция освобождения вызывается независимо от того, генерирует ли деструктор для объекта или некоторого элемента массива исключение. - конечная нота ]

Я попробовал следующий код в G ++, и он показывает, что больше никаких деструкторов не вызывается после исключения:

#include <iostream>
int id = 0;
class X
{
    public:
         X() {   me = id++; std::cout << "C: Start" << me << "\n";}
        ~X() {   std::cout << "C: Done " << me << "\n";
                 if (me == 5) {throw int(1);}
             }
    private:
        int me;
};

int main()
{
    try
    {
        X       data[10];
    }
    catch(...)
    {
        std::cout << "Finished\n";
    }
}

Execute:

> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished

Что все приводит к этому (очень старый ответ):
выбрасывание исключений из деструктора

5 голосов
/ 28 июня 2011

5.3.5.7 Если значение операнда delete-выражение не является нулевым значение указателя, выражение удаления вызовет функцию освобождения (3.7.3.2). В противном случае он не указан будет ли функция освобождения называться. [Примечание: освобождение функция вызывается независимо от ли деструктор для объекта или какой-то элемент массива бросает исключение. - конец примечания]

Не могу найти ничего о деструкторах, кроме

В случае массива элементы будут уничтожается в порядке убывания адреса (то есть в обратном порядке завершения их конструктора; см. 12.6.2).

Полагаю, что после броска больше не вызывается деструктор, но я не уверен.

5 голосов
/ 28 июня 2011

Никогда не делай этого.Если уже существует активное исключение, будет вызвано std::terminate: "Bang, you dead" .Твой деструктор должен.Не.Бросьте.Сопротивление.


редактировать: соответствующий раздел из стандарта (14882 2003), 15,2 Конструкторы и деструкторы [except.dtor]:

15,2.3 Процесс вызова деструкторов для автоматических объектов, созданных на пути от блока try к выражению throw, называется «размоткой стека». [Примечание: Если деструктор, вызванный при разматывании стека, завершается с исключениемЗавершение называется (15.5.1) .Таким образом, деструкторы должны, как правило, перехватывать исключения и не позволять им распространяться за пределы деструктора.- конец примечания]


Тестовый пример для игры ( в реальной жизни, выбросить что-то, полученное из std::exception, никогда не бросать int или что-то еще! ):

    #include <iostream>
    int main() {
        struct Foo {
            ~Foo() {
                throw 0; // ... fore, std::terminate is called.
            }
        };

        try {
            Foo f;
            throw 0; // First one, will be the active exception once Foo::~Foo()
                     // is executed, there- ...
        } catch (int) {
            std::cout << "caught something" << std::endl;
        }
    }
2 голосов
/ 28 июня 2011

Хорошо, вот некоторый экспериментальный код:

#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>

void* operator new[](size_t size) throw (std::bad_alloc)
{
    std::cout << "allocating " << size << " bytes" << std::endl;
    return malloc(size);
}

void operator delete[](void* payload) throw ()
{
    std::cout << "releasing memory at " << payload << std::endl;
    free(payload);
}

struct X
{
    bool throw_during_destruction;

    ~X()
    {
        std::cout << "destructing " << (void*)this << std::endl;
        if (throw_during_destruction) throw 42;
    }
};

int main()
{
    X* p = new X[10]();
    p[5].throw_during_destruction = true;
    p[1].throw_during_destruction = true;
    delete[] p;
}

Запуск кода дал следующий вывод на g ++ 4.6.0:

allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'

This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.

Так что может показаться, что std::terminateвызывается сразу, как только первый деструктор бросает.Другие элементы не разрушаются, и память не освобождается.Кто-нибудь может это подтвердить?

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

Чтобы ответить на ваш второй вопрос, если бы вы использовали вместо него std :: vector, не было бы необходимости в вызове для удаления, вы не используете указатели (я полагаю, что класс vector внутренне, но это не так).до вас, чтобы управлять).

0 голосов
/ 28 июня 2011

Если выброшено исключение, оно выбрасывается.Объект, который не удалось уничтожить, явно не должным образом уничтожен, и ни один из них не остается в массиве.

Если вы используете вектор, проблема та же, но не в вашейкод.: -)

Итак, бросать деструкторы - это просто плохая идея (tm).


Как показывает @Martin ниже, брошенный объект формально не существует, как толькомы входим в деструктор.У других тоже может быть восстановлена ​​память.

Однако в ней, очевидно, содержались некоторые сложные вещи, которые не были должным образом очищены от промывки.Если этот объект и другие, следующие за ним в массиве, содержали некоторые блокировки мьютексов, открытые файлы, кэши баз данных или shared_ptrs, и ни у одного из них не было запущено деструкторов , мы, вероятно, столкнулись с большими проблемами.

Вызов std :: terminate в этот момент, чтобы избавить программу от страданий, выглядит как то, чего вы бы хотели!

...