Как освободить память после исключения в 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();
}