2011-08-05 25 views
6

Exceptional C++ cita il seguente codiceC++ sicurezza rispetto alle eccezioni

template <class T> class Stack 
{ 
    public: 
     Stack(); 
     ~Stack(); 

     /*...*/ 

    private: 
     T*  v_;  // ptr to a memory area big 
     size_t vsize_; // enough for 'vsize_' T's 
     size_t vused_; // # of T's actually in use 
}; 



template<class T> 
Stack<T>::Stack() 
     : v_(new T[10]), // default allocation 
      vsize_(10), 
      vused_(0)  // nothing used yet 
{ 
} 

Si dice che se uno dei costruttori T gettato, poi tutti gli oggetti T che sono stati interamente costruiti erano adeguatamente distrutti e, infine, operatore delete è stato chiamato automaticamente per rilasciare la memoria. Questo ci rende a prova di perdite.

La mia comprensione era che se un costruttore genera un'eccezione, l'applicazione dovrebbe ripulire eventuali risorse allocate. Come è tenuta la suddetta?

risposta

8

Citando lo standard C++ 03, §5.3.4/8:

A nuova espressione ottiene stoccaggio per l'oggetto chiamando una funzione allocazione.Se la nuova espressione termina lanciando un'eccezione, può rilasciare memoria richiamando una funzione di deallocazione. Se il tipo allocato è di tipo non array, il nome della funzione di allocazione è operator new e il nome della funzione di deallocazione è operator delete. Se il tipo allocato è un tipo di matrice, il nome della funzione di allocazione è operator new[] e il nome della funzione di deallocazione è operator delete[].

§5.3.4/17:

Se una parte dell'inizializzazione scopo sopra descritto termina lanciando una funzione deallocazione adatto eccezione e può essere trovato, la funzione deallocazione è chiamato a liberare la memoria in cui è stato costruito l'oggetto, dopo il quale l'eccezione continua a propagarsi nel contesto della nuova espressione .

conseguenza, eventuali T genera un'eccezione, il runtime distruggerà qualsiasi sottoggetti già creati dell'istanza T cui costruttore gettato, quindi chiamare operator delete[] sull'array nel suo complesso, distruggendo eventuali elementi già creati e deallocare la memoria dell'array.

+0

ti fa chiedere quanto bene funzionano le implementazioni: se il costruttore di 'v_ [5]' gettato, poi sarebbe 'eliminare v _ []' solo distruggere 'v_ [0]' attraverso 'v_ [4]', e saltare quelle non costruito? –

+0

@Mike: quelli non costruiti non devono essere distrutti (non sono mai stati creati); cosa stai dicendo che il problema è? – ildjarn

+0

Mi chiedo se va e li distrugge comunque. (Ie vuol perdere traccia di quante si costruisce con successo?) Ora che ci penso, mi chiedo se assicura la distruzione in ordine inverso di costruzione ... –

3

[Correzione:] Non lo è. Un'eccezione nel costruttore non perderà risorse perché l'unica posizione che potrebbe verificarsi un'eccezione è all'interno dell'espressione new e, in caso di errore di un'espressione new, le risorse che sono state allocate da tale oggetto vengono liberate. La tua situazione è speciale perché crei una sola allocazione nel costruttore - in generale non è sicuro!

vostra frase citata si riferisce è l'operatore delete per l'oggetto-fallito il cui costruttore ha gettato:

struct T 
{ 
    T() { throw 1; } 
    char data[200]; 
}; 

// in your code: 

T * pt = new T; 

Nell'ultima riga, la memoria viene allocata prima che il costruttore viene richiamato. La memoria viene rilasciata in caso di eccezione, mediante una chiamata automatica a ::operator delete(pt). (In generale, l'abbinamento delete-operatore (non "espressione") che corrisponde al nuova espressione si chiama!).

Va in questo modo:

  • costruzione di successo: 1. Ripartizione. 2. Costruzione. 3. Distruzione. 4. Deallocazione.

  • Costruzione non riuscita: 1. Assegnazione. 2. Deallocazione.

Nota che abbiamo solo abbiamo un oggetto dopo il costruttore ha completato - in modo in caso di un'eccezione nel costruttore, noi non hanno nemmeno un oggetto. Ecco perché ho detto "oggetto fallito" sopra con un trattino, perché non è affatto un oggetto (come il Douglas-fir non è affatto un abete).

Il tuo codice è potenzialmente interamente perdita non sicuro, se stai facendo più di un'assegnazione che potrebbe lanciare - cioè una perdita si verifica ogni volta che un oggetto è stato costruito con successo ma un altro, uno successivo non riesce. Si dovrebbe probabilmente solo non chiamare new nella lista di inizializzazione e invece metterlo nel corpo:

class Danger 
{ 
    T * pt1, * pt2; 
public: 
    Danger() 
    { 
    try { pt1 = new T; } catch(...) { throw(); } 
    try { pt2 = new T; } catch(...) { delete pt1; throw(); } 
    } 
}; 

Oppure, dal principio della responsabilità unica, non utilizzare i puntatori prime ma contenitori che gestiscono l'utilizzo delle risorse che pulire dopo loro stessi!!

+2

Sono completamente in disaccordo, ma sono titubante per il downvote perché in genere sei un tipo piuttosto intelligente. ; -] Alla luce degli standard citati nella mia risposta, pensi davvero che il codice dell'OP possa perdere? – ildjarn

+0

Non capisco come i dati di carattere [] vengano distrutti. –

+0

@ildjarn: Controlla il mio aggiornamento; il codice effettivo nell'OP è probabilmente valido, ma non appena aggiungi qualcosa al costruttore che potrebbe lanciare, sei nei guai. L'unica cosa che è protetta dalla perdita è l'oggetto stesso. –

0

Provare il puntatore automatico per T* v_ o qualsiasi risorsa allocata dinamica. Se accade seguente

template<class T> 
Stack<T>::Stack() 
     : v_(new T[10]), 
      vsize_(10), 
      vused_(0) 
{ 
    throw 0; // terminated by exception 
} 

o, c'è un altro oggetto nella Stack un'eccezione nel costruire, v_ causerà perdita di memoria. Se lo si avvolge come std::unique_ptr<T[]> v_ o qualcosa di simile, verrà automaticamente liberato se il costrutto di Stack termina con un'eccezione.

+0

Se con "puntatore automatico" si intende 'std :: auto_ptr', l'uso di quello con un array richiama il comportamento indefinito perché chiama' delete' piuttosto che 'delete []'. – ildjarn

+0

@ildjarn Mi scusi "auto" qui significa gestione automatica delle risorse, non 'std :: auto_ptr'. Come vedi, io uso 'std :: unique_ptr' nel mio consiglio, giusto? – neuront

+0

In effetti, volevo solo chiarire; Non ho certamente alcun problema nel consigliare 'unique_ptr'. : -] – ildjarn

Problemi correlati