2013-07-29 9 views
6

Mi scuso se questa domanda è un duplicato - ho cercato per un po ', ma è possibile che il mio Google-fu non sia all'altezza.Come dovrebbe essere liberata la memoria dopo che un'eccezione è stata lanciata in C++?

Sto modificando un programma C++ che chiama in una libreria C. La libreria C assegna un po 'di memoria (usando malloc()), e il programma C++ lo usa e poi lo libera. Il problema è che il programma C++ può lanciare un'eccezione a metà dell'esecuzione, facendo in modo che la memoria allocata non venga mai liberata.

Come un (piuttosto artificiosa) Esempio:

/* 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 
} 

La mia domanda è: come debbo fare con questo? La mia prima idea era quella di avvolgere il tutto in un blocco try/catch, e nel catch basta controllare e liberare la memoria e ri-lanciare l'eccezione, ma questo mi sembra sgraziato e goffo (e non funzionerebbe bene se io voglio effettivamente catturare un'eccezione). C'è un modo migliore per farlo?

EDIT: Probabilmente avrei dovuto dire che stiamo usando g ++ 4.2.2, dal 2007, prima che fosse introdotto std :: unique_ptr. Chalk fino all'inerzia aziendale.

+1

Perché non puoi semplicemente liberare la memoria prima di lanciare l'eccezione? –

+13

Usa RAII, problema risolto. – Borgleader

risposta

7

Usa std::unique_ptr con una delezione personalizzato che chiama gratis:

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

Questa probabilmente sarebbe la risposta giusta, tranne che stiamo usando una vecchia versione di g ++ (vedi la modifica sopra). – Dan

+6

Scrivi la tua sostituzione unique_ptr, quindi spiega al tuo capo perché hai trascorso 2 giorni a eseguire il debug di uno strumento linguistico di base che avresti ottenuto gratuitamente se consentito l'aggiornamento. Ripeti finché non iniziano a pesare il tempo sprecato con strumenti obsoleti rispetto al tempo impiegato per l'aggiornamento. – DanielKO

4

È necessario assicurarsi che non si butta solo dopo aver liberato la memoria - o che utilizzare una idonea struttura puntatore intelligente per memorizzare il mem, in modo tale che quando il throw accade, e lo stack snoda, il mem viene liberato.

1

C'è qualche motivo per non liberare semplicemente la memoria all'interno della clausola if?

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

Duplicazione del codice, anche se in questo caso specifico il 'libero' potrebbe essere spostato completamente prima del condizionale. A lungo termine, c'è il pericolo che qualcuno ignaro di ciò che sta accadendo qui possa inserire un altro 'throw' o' return' o chiamare un'altra funzione che potrebbe lanciare senza gestire la fragile risorsa. RAII protegge dal prossimo ragazzo che è stupido. – Casey

4

Wrap quel briccone:

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. Puoi spiegare l'uso di 'char []'? – sehe

+0

'unique_ptr ' è una specializzazione parziale che sovraccarica 'operator []' invece di 'operator ->' e usa 'delete []' di default (non importante qui). – Casey

+0

Sono eternamente confuso con quello, quindi. Pensavo che "shared_ptr" avesse questo. Ma a quanto pare, hanno dimenticato di aggiungere lo stesso per 'shared_ptr' – sehe

1

Uso 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 sostituire 'unique_ptr' di [' boost :: scoped_ptr'] (http://www.boost.org/doc/libs/1_54_0/libs/smart_ptr/scoped_ptr.htm) se non ce l'hai – sehe

2

Dal y ou're usando una vecchia versione del compilatore che non ha unique_ptr, è possibile scrivere il RAII involucro voi stessi:

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

Basta fare attenzione a non per consentire copia/assegnazione, anche in modo implicito (che è il motivo per cui ho fatto m_ptr const) o rischi di finire con la doppia liberazione della memoria (la semantica "sposta" alla auto_ptr è meglio evitare a meno che non sia necessario il ).

2

Poiché non è possibile utilizzare std::unique_ptr, è possibile creare la propria classe deleter che controllerebbe la durata del puntatore in modo RAII. Per semplificare questo esempio non avvolge il puntatore reale ma esiste al suo fianco; un approccio più sicuro sarebbe quello di creare una vera classe di puntatore intelligente.

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

Mostrato in azione: http://ideone.com/iLgXdY –

Problemi correlati