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

Я извиняюсь, если этот вопрос является дубликатом - я искал некоторое время, но вполне возможно, что мои Google-фу не проведешь.

Я изменяю программу на 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 был введен. Списать это на корпоративную инерцию.

7 ответов


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

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

void my_class::my_func() {
    std::unique_ptr<char, free_mem> mem = allocate_lots();

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


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

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");
}

так как вы используете старую версию компилятора, которая не имеет 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) или вы рискуете в конечном итоге с двойным освобождением памяти (семантика" move " à la auto_ptr лучше избегать, если вы не абсолютно это нужно).


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

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");
}

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

if (problem) {
    free (mem);
    throw my_exception ("Drat!");
}

использовать 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();
}