2013-07-29 4 views
6

Приносим извинения, если этот вопрос является дубликатом - я искал какое-то время, но возможно, что мой Google-фу просто не в состоянии понюхать.Как освободить память после исключения на 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. Мел это до корпоративной инерции.

+1

Почему вы не можете просто освободить память перед тем, как выбросить исключение? –

+13

Использование RAII, проблема решена. – Borgleader

ответ

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(); 
+0

Возможно, это будет правильный ответ, за исключением того, что мы используем старую версию g ++ (см. Выше). – Dan

+6

Напишите свою уникальную замену unique_ptr, а затем объясните своему боссу, почему вы потратили 2 дня на отладку базового языкового инструмента, который вы получили бы бесплатно, если бы они разрешили модернизацию. Повторяйте это до тех пор, пока они не начнут взвешивать время, потраченное на устаревание инструментов против времени, потраченного на модернизацию. – DanielKO

4

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

1

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

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

Дублирование кода, хотя в этом конкретном случае «свободный» действительно может быть перемещен до условного завершения. В долгосрочной перспективе существует опасность того, что кто-то не знает, что здесь происходит, может вставить еще один «бросок» или «возврат» или вызов другой функции, которая может бросаться без обработки хрупкого ресурса. RAII защищает против того, чтобы следующий парень был глуп. – Casey

4

Wrap, что негодяй:

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

Mmm. Можете ли вы объяснить использование 'char []'? – sehe

+0

'unique_ptr ' - это частичная специализация, которая перегружает 'operator []' вместо 'operator ->' и по умолчанию использует 'delete []' (здесь здесь нет значения). – Casey

+0

Тогда я вечно смущен этим. Я думал, что у 'shared_ptr' есть это. Но, по-видимому, они забыли добавить то же самое для 'shared_ptr' – sehe

1

Использование 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(); 
} 
+2

@Dan заменить 'unique_ptr' на [' boost :: scoped_ptr'] (http://www.boost.org/doc/libs/1_54_0/libs/smart_ptr/scoped_ptr.htm), если у вас его нет – sehe

2

Так как у ou're использует старую версию компилятора, который не имеет 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 лучше всего избегать, если вы не абсолютно это необходимо).

2

Поскольку вы не можете использовать std::unique_ptr, вы можете создать свой собственный класс deleter, который будет управлять временем жизни указателя в режиме 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"); 
} 
+0

Показано в действии: http://ideone.com/iLgXdY –

Смежные вопросы