Как освободить память в блоках try-catch? - PullRequest
24 голосов
/ 15 июня 2010

Надеюсь, у меня простой вопрос: как освободить одну память, выделенную в блоке try, когда возникает исключение?Рассмотрим следующий код:

try
 {
  char *heap = new char [50];
        //let exception occur here
  delete[] heap;
 }
 catch (...)
 {
  cout << "Error, leaving function now";
  //delete[] heap; doesn't work of course, heap is unknown to compiler
  return 1;
 }

Как освободить память после выделения кучи и возникновения исключения перед вызовом delete[] heap?Есть ли правило не выделять память в куче в этих попытках .. ловить блоки?

Ответы [ 9 ]

34 голосов
/ 15 июня 2010

Изучите идиому RAII ( Получение ресурсов - инициализация )! Смотрите, например статья Википедии о RAII .

RAII - это просто общая идея. Он используется, например, в стандартных библиотеках C ++ std::unique_ptr или std::shared_ptr шаблонных классов.


Очень краткое объяснение идиомы RAII:

По сути, это C ++ версия блоков try..finally, найденная в некоторых других языках. Идиома RAII, возможно, более гибкая.

Работает так:

  • Вы пишете класс-оболочку для своего ресурса (например, памяти). Деструктор отвечает за освобождение ресурса.

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

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


Очень грубый пример:

// BEWARE: this is NOT a good implementation at all, but is supposed to
// give you a general idea of how RAII is supposed to work:
template <typename T>
class wrapper_around
{
  public:
    wrapper_around(T value)
        : _value(value)
    { }
    T operator *()
    {
        return _value;
    }
    virtual ~wrapper_around()
    {
        delete _value;  // <-- NOTE: this is incorrect in this particular case;
                        // if T is an array type, delete[] ought to be used
    }
  private:
    T _value;
};
// ...

{
    wrapper_around<char*> heap( new char[50] );
    // ... do something ...

    // no matter how the { } scope in which heap is defined is exited,
    // if heap has a destructor, it will get called when the scope is left.
    // Therefore, delegate the responsibility of managing your allocated
    // memory to the `wrapper_around` template class.
    // there are already existing implementations that are much better
    // than the above, e.g. `std::unique_ptr` and `std::shared_ptr`!
}
9 голосов
/ 15 июня 2010

ОК, мистер Java-программист:

try
{
    // Exception safe dynamic allocation of a block of memory.
    std::vector<char>  heap(50);

    // DO STUFF

    // Note in C++ we use stack based objects and their constructor/destructor
    // TO give a deterministic cleanup, even in the presence of exceptions.
    //
    // Look up RAII (bad name for a fantastic concept).
}
catch (...)
{
    cout << "Error, leaving function now";
    return 1;  // Though why you want to return when you have not fixed the exception is
               // slightly strange. Did you want to rethrow?
}
8 голосов
/ 16 июня 2010

Общий ответ - RAII.

Тем не менее, ее можно решить, переместив переменную из области действия try {}:

char * heap = NULL;
try {
  heap = new char [50];
  ... stuff ...
} catch (...) {
  if (heap) {
    delete[] heap;
    heap = NULL;
  }
  ... however you want to handle the exception: rethrow, return, etc ...
}

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

Мир

6 голосов
/ 15 июня 2010

Либо переместите new перед try, чтобы указатель все еще находился в области видимости, либо используйте умный указатель, например shared_ptr или unique_ptr (в крайнем случае, auto_ptr, но у него есть проблемы ) который уберет вас при выходе. Исключения - огромная причина, по которой умные указатели важны.

3 голосов
/ 15 июня 2010

«Правильный» ответ - RAII и shared_ptr, как упомянуто выше, но для простоты: в вашем примере вы могли бы заменить

char *heap = new char [50];

с

char *stack = static_cast<char*>( alloca(50) );

alloca практически идентичен malloc, за исключением того, что он выделяет память в стеке, а не в куче, поэтому независимо от того, как вы выполняете функцию (бросая или сейчас), память будет возвращена, и никакие удаления или нужны освобождения.

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

Я должен согласиться со всеми, кто сказал RAII, однако я бы использовал Boost's shared_array вместо auto_ptr.Автоматический указатель вызывает delete, а не 'delete []', что приведет к утечкам в массиве.

0 голосов
/ 23 июня 2010

Да - если вы рассматриваете простоту - решение, которое является внешним по отношению к вашему блоку try, является решением.

Привет

0 голосов
/ 15 июня 2010

Согласен с ответами на RAII и умными указателями.

Однако, если вы настаиваете, вы можете сделать это:

try { dangerous operations } 
catch { cleanup; throw; }
0 голосов
/ 15 июня 2010

Самый простой способ - объявить переменную перед блоком try, а затем просто выполнить инициализацию внутри блока.

...