Как следует освобождать память после возникновения исключения в C++?

Прошу прощения, если этот вопрос повторяется - я некоторое время искал, но, возможно, мой гугл-фу просто не на высоте.

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

В качестве (довольно надуманного) примера:

/* old_library.c */
char *allocate_lots() {
    char *mem = (char *)malloc(1024);
    return mem;
}

/* my_prog.cpp */
void my_class::my_func () {
    char *mem = allocate_lots();
    bool problem = use(mem);
    if (problem)
        throw my_exception("Oh noes! This will be caught higher up");
    free(mem);  // Never gets called if problem is true
}

У меня вопрос: как мне быть с этим? Моя первая идея заключалась в том, чтобы обернуть все это в блок try/catch, а в блоке catch просто проверить и освободить память и повторно выдать исключение, но мне это кажется некрасивым и неуклюжим (и не будет работать хорошо, если я хотите фактически поймать исключение). Есть ли лучший способ сделать это?

EDIT: Вероятно, мне следовало упомянуть, что мы используем g++ 4.2.2 еще в 2007 году, до того, как был представлен std::unique_ptr. Спишите это на корпоративную инерцию.


person Dan    schedule 29.07.2013    source источник
comment
Почему вы не можете просто освободить память, прежде чем создавать исключение?   -  person Scotty Bauer    schedule 30.07.2013
comment
Используйте RAII, проблема решена.   -  person Borgleader    schedule 30.07.2013


Ответы (7)


Используйте std::unique_ptr с пользовательским средством удаления, которое вызывает бесплатно:

class free_mem {
public:
    void operator()(char *mem) { free(mem); }
};

void my_class::my_func() {
    std::unique_ptr<char, free_mem> mem = allocate_lots();
person Chris Dodd    schedule 29.07.2013
comment
Вероятно, это был бы правильный ответ, за исключением того, что мы используем старую версию g++ (см. редактирование выше). - person Dan; 30.07.2013
comment
Напишите свою собственную замену unique_ptr, а затем объясните своему боссу, почему вы потратили 2 дня на отладку базового языкового инструмента, который вы бы получили бесплатно, если бы разрешили обновление. Повторяйте это до тех пор, пока они не начнут взвешивать время, потраченное на устаревшие инструменты, по сравнению со временем, затраченным на обновление. - person DanielKO; 30.07.2013

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

person Mats Petersson    schedule 29.07.2013

Оберните этого негодяя:

struct malloc_deleter {
  template <typename T>
  void operator () (T* p) const {
    free(p);
  }
};

void my_class::my_func () {
    std::unique_ptr<char[],malloc_deleter> mem{allocate_lots()};
    bool problem = use(mem.get());
    if (problem)
        throw my_exception("Oh noes! This will be caught higher up");
}
person Casey    schedule 29.07.2013
comment
М-м-м. Можете ли вы объяснить использование char[]? - person sehe; 30.07.2013
comment
unique_ptr<T[]> — это частичная специализация, которая перегружает operator [] вместо operator -> и использует delete[] по умолчанию (очевидно, здесь это не важно). - person Casey; 30.07.2013
comment
Тогда я вечно смущен этим. Я думал, что это было у shared_ptr. Но видимо забыли добавить то же самое для shared_ptr - person sehe; 30.07.2013
comment
Правильный. boost::shared_ptr имеет специализацию T[], но она была добавлена ​​после того, как стандарт поглотил shared_ptr в TR1. (См. эту тему). - person Casey; 30.07.2013
comment
Вероятно, это был бы правильный ответ, за исключением того, что мы используем старую версию g++ (см. редактирование выше). - person Dan; 30.07.2013

Поскольку вы используете старую версию компилятора, в которой нет unique_ptr, вы можете сами написать свою оболочку RAII:

class ResourceWrapper {
public:
    ResourceWrapper(char* ptr) : m_ptr(ptr) {}
    ~ResourceWrapper() { free(m_ptr); }
    // whatever getters suit you, at the very least:
    char* get() const { return m_ptr; }
private:
    char* const m_ptr;
};

void my_class::my_func () {
    ResourceWrapper mem(allocate_lots());
    bool problem = use(mem.get());
    if (problem)
        throw my_exception("Oh noes! This will be caught higher up");
}

Просто убедитесь, что не разрешено копирование/назначение даже неявно (именно поэтому я сделал m_ptr const), иначе вы рискуете получить двойное освобождение памяти (лучше всего использовать семантику перемещения а-ля auto_ptr). следует избегать, если вы абсолютно не нуждаетесь в этом).

person syam    schedule 29.07.2013

Поскольку вы не можете использовать std::unique_ptr, вы можете создать свой собственный класс удаления, который будет контролировать время жизни указателя в стиле RAII. Для простоты этот пример не является оболочкой для фактического указателя, но существует рядом с ним; более безопасным подходом было бы создание класса интеллектуальных указателей.

class AutoFree
{
public:
    AutoFree(void* p) : m_p(p)
    {
    }
    ~AutoFree()
    {
        free(m_p);
    }
private:
    void* m_p;
};

void my_class::my_func () {
    char *mem = allocate_lots();
    AutoFree mem_free(mem);
    bool problem = use(mem);
    if (problem)
        throw my_exception("Oh noes! This will be caught higher up");
}
person Mark Ransom    schedule 29.07.2013

Есть ли причина не просто освободить память внутри предложения if?

if (problem) {
    free (mem);
    throw my_exception ("Drat!");
}
person verbose    schedule 29.07.2013
comment
Дублирование кода, хотя в этом конкретном случае free действительно можно вообще переместить перед условным оператором. В долгосрочной перспективе существует опасность того, что кто-то, не знающий, что здесь происходит, может вставить еще один throw или return или вызвать другую функцию, которая может вызвать исключение без обработки хрупкого ресурса. RAII защищает от глупости следующего парня. - person Casey; 30.07.2013

Используйте unique_ptr: http://coliru.stacked-crooked.com/view?id=cd3f0fc64d99cc07a2350e2ff9686500-542192d2d8aca3c820c7acc656fa0c68

#include <stdexcept>
#include <iostream>

#include <memory>

/* old_library.c */
char *allocate_lots()
{
    return static_cast<char*>(malloc(1024));
}

struct my_exception : virtual std::exception {
    const char* const msg;
    my_exception(const char* const msg) : msg(msg) {}
    const char* what() const noexcept { return msg; }
};

struct my_class
{
    struct Free { void operator() (char* p) const { free(p); } };
    /* my_prog.cpp */
    void my_func()
    {
        std::unique_ptr<char, Free> mem;

        mem.reset(allocate_lots());
        bool problem = use(mem.get());

        if(problem)
        {
            throw my_exception("Oh noes! This will be caught higher up");
        }
    }

    static bool use(char*) { return true; }
};

int main()
{
    my_class prog;
    prog.my_func();
}
person sehe    schedule 29.07.2013
comment
@Dan замените unique_ptr на boost::scoped_ptr, если вы этого не сделаете. у меня это есть - person sehe; 30.07.2013