2013-08-28 11 views
5
class XX 
{ 
public: 
    static unsigned s_cnt; 
    XX() 
    { 
     ++ s_cnt; 
     std::cout << "C XX " << s_cnt << "\n"; 

     if (s_cnt > 2) 
      throw std::exception(); 
    } 

    //private: 
    ~XX() 
    { 
     std::cout << "~ XX\n"; 
    } 
}; 
unsigned XX::s_cnt = 0; 

int main() 
{ 
    try 
    { 
     XX *xx = new XX[10]; 
    } catch (...) 
    { 
     std::cout << "Exc\n"; 
    } 
} 

uscita:Perché i distruttori non vengono chiamati quando un'eccezione non rilevata viene lanciata in C++ durante la creazione dell'array?

C XX 1 
C XX 2 
C XX 3 
~ XX 
~ XX 
Exc 

Ma quando rimuovo try-catch, vedo:

C XX 1 
C XX 2 
C XX 3 
terminate called after throwing an instance of 'std::exception' 
    what(): std::exception 
zsh: abort  ./a.out 

Perché C++ chiamare i distruttori in primo caso ma non nel secondo?

risposta

13

Quando non si rileva un'eccezione (cioè diventa un'eccezione non rilevata e termina il programma), C++ non offre alcuna garanzia che i distruttori vengano effettivamente chiamati.

Ciò fornisce al margine del compilatore come implementare la gestione delle eccezioni. Ad esempio, GCC prima cerca un gestore. Se non riesce a trovarne uno, si interrompe immediatamente, conservando le informazioni complete sullo stack per il debug. Se ne trova uno, in realtà svolge lo stack, distruggendo oggetti, finché non arriva al gestore. Ecco perché non vedi l'output: il programma si interrompe prima di distruggere qualsiasi oggetto.

+1

È possibile aggiungere che la ragione di ciò è che non si perdono le informazioni di stato nel core dump. –

+0

Buon punto, aggiunto. –

3

Quando si genera un'eccezione da un costruttore, lo standard considera l'oggetto come non creato. Non distruggi qualcosa che non esiste, vero?

In realtà, anche questo semplice esempio non funziona come lei suggerisce:

struct A { 
    A() {throw std::exception();} 
    ~A() {std::cout << "A::~A()" << std::endl;} 
}; 

int main() { 
    A a; 
} 

Questa termina con un'eccezione non rilevata, ma non viene stampata "A :: ~ A()".

Se ci pensi, è l'unico modo possibile per dare un semantico ad esso.

Ad esempio, possiamo usare il nostro tipo A come un oggetto di membro di una classe B:

struct B { 
    B() : m_a(),m_p(new int) {} 
    ~B() {delete m_p;} 

    A m_a; 
    int * m_p; 
}; 

Ovviamente B::B() getta immediatamente (e non anche inizializzare m_p). Se un ipotetico standard C++ richiede chiamate allo B::~B() in questo caso, il distruttore non ha modo di sapere se m_p è stato inizializzato o meno.

In altre parole, il lancio di un'eccezione da un costruttore significa che l'oggetto non è mai esistito e la sua durata non è mai iniziata. Questo GotW è abbastanza chiaro a riguardo.

Bonus: cosa succede se nella definizione di B scambiamo l'ordine di m_a e m_p? Quindi si verifica una perdita di memoria. Ecco perché esiste una sintassi particolare per rilevare l'eccezione durante l'inizializzazione degli oggetti membri.

+0

new XX [10] costruisce gli oggetti uno per uno. I primi 2 oggetti sono costruiti senza alcun problema. Quindi tenta di costruire il terzo e fallisce. Quindi chiama il distruttore per XX [1] e XX [0], ma NON XX [2]. In effetti hai solo 2 righe '~ XX' nell'output, non 3. – sbabbi

+0

Mi riferivo al secondo caso, in cui il blocco' try' è stato rimosso e non sono stati chiamati i distruttori, nonostante gli oggetti siano stati costruiti. Non stai affrontando quella parte della domanda. – juanchopanza

+0

Beh, mi sono perso la modifica in cui hai cambiato il titolo della domanda, il mio male. La seconda parte è indirizzata dalla risposta @Sebastian Redl. Ma non ha nulla a che fare con il fatto che stai lavorando con gli array. – sbabbi

Problemi correlati