2009-07-09 13 views
13

Ho appena iniziato con RAII in C++ e ho impostato un piccolo test case. O il mio codice è profondamente confuso, o RAII non funziona! (Immagino sia il primo).C++ RAII non funziona?

Se corro:

#include <exception> 
#include <iostream> 
class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

int main(void) { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 

ad eccezione commentata ottengo:

A 1 constructed 
A 2 constructed 
A 2 destructed 
A 1 destructed 

come previsto, ma con l'eccezione ottengo:

A 1 constructed 
A 2 constructed 
terminate called after throwing an instance of 'std::exception' 
    what(): std::exception 
Aborted 

così i miei oggetti non vengono distrutti anche se stanno andando fuori dal campo di applicazione. Non è questa l'intera base per RAII.

Puntatori e correzioni molto apprezzati!

+5

Anche tu hai trovato un bug in C++! =) – Eric

+0

caso limite interessante! –

+2

Hai rotto RAII :( – rpg

risposta

6

Si dispone di un'eccezione non gestita nel main, che significa una chiamata da terminare. Prova:

int main(void) 
{ 
    try 
    { 
     A a1(1); 
     A a2(2); 
     throw std::exception(); 
     return 0; 
    } 
    catch(const std::exception & e) 
    { 
     return 1; 
    } 


} 
+0

Quindi devo sempre racchiudere main in try/catch quando usi RAII? – John

+2

Penso che main() sia diverso. Prova a mettere tutto in una funzione che chiami da main(). –

+1

No, solo quando fai qualcosa che può generare un'eccezione. – Alex

3

Non si sta gestendo correttamente l'eccezione, quindi l'applicazione si sta esaurendo prima che gli oggetti escano dall'ambito.

Ho intenzione di spiegare un po 'di più. Se un'eccezione "bolle" fino a main lo stack viene svolto (modifica). Anche lo spostamento del codice in una funzione secondaria non risolverà questo problema. es:

 1 #include <exception> 
     2 #include <iostream> 
     3 
     4 void test(); 
     5 
     6 class A { 
     7  public: 
     8   A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
     9   ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
    10  private: int i_; 
    11 }; 
    12 
    13 
    14 int main(void) { 
    15  test(); 
    16  return 0; 
    17 } 
    18 
    19 void test(){ 
    20    A a1(1); 
    21    A a2(2); 
    22   throw std::exception(); 
    23 } 

Il codice di cui sopra non si risolvere il problema. L'unico modo per risolvere questo è avvolgere l'eccezione generata in un blocco try-catch. Ciò manterrà l'eccezione dal raggiungimento del main e interromperà la terminazione che si sta verificando prima che gli oggetti escano dall'ambito.

+0

Questa è una risposta corretta, -1? Veramente? – Alex

+0

Anche se si sposta il codice su un'altra funzione se non si dispone di un try/catch, esso diventerà il bubble principale e causerà lo stesso problema. Quindi "main" non è affatto speciale. – Alex

+0

Ti stai sbagliando sul "comportamento indefinito". Il comportamento di un'eccezione senza un gestore è definito nello standard, tranne per il fatto che lo stallo non viene svolto o meno è definita dall'implementazione. –

20

Non hai un gestore per la tua eccezione. Quando questo accade, lo standard dice che std :: terminate viene chiamato, che a sua volta chiama abort. Vedere la sezione 14.7 in The C++ Programming Language, 3rd edition.

+0

In realtà è possibile impostare la propria funzione di terminazione, ma non so che sia una funzionalità davvero utile. –

+2

Bene, potrebbe essere usato, per esempio, per registrare la terminazione su un file specifico o inviare una e-mail a qualcuno. –

+0

Come RaphaelPS ha sottolineato, la scrittura della propria funzione di terminazione è utile se si desidera registrare la terminazione o pulire le risorse globali. Non è possibile, tuttavia, recuperare da terminare. Un gestore terminato non accetta argomenti, non ha alcun valore di ritorno e dovrebbe uscire dall'applicazione. –

17

Il problema è che main ha uno stato speciale. Quando viene lanciata un'eccezione da lì, lo stack non può essere liquidato in modo significativo, l'applicazione chiama invece std:terminate.

E poi ha un po 'di senso perché le variabili non vanno oltre lo scopo. In realtà non abbiamo lasciato l'ambito in cui sono stati dichiarati. Ciò che accade potrebbe essere considerato equivalente a questo:

int main(void) { 
    A a1(1); 
    A a2(2); 
    std::terminate(); 
} 

(credo che sia definito dall'implementazione se distruttori sono chiamati in questo caso, però, quindi su alcune piattaforme, funzionerà come vi aspettavate)

+0

Perché viene definita l'implementazione? Non avrebbe più senso se i costruttori venissero sempre chiamati? C++ può essere semplicemente sciocco a volte ... – Zifre

+2

Non stupido - pratico. Costringere i distruttori (non i costruttori) a essere sempre chiamati limiterebbe i modi in cui EH e lo srotolamento dello stack possono essere implementati. –

+3

Può essere sciocco, ma è anche pragmatico. Le specifiche C++ cercano di evitare la limitazione degli implementatori. Idealmente, vogliono che sia possibile creare un'implementazione conforme allo standard di C++ su * any * CPU e * any * OS. E poiché la funzione principale, in un certo senso, segna il confine tra OS e C++, non vogliono assumere troppe cose a riguardo. Hai ragione, nel mondo reale, sarebbe bello se non lasciassero questo particolare dettaglio fino all'implementazione. Lo sappiamo ora, ma era ovvio nel 1998, quando la lingua veniva standardizzata? Non volevano dipingersi in un angolo – jalf

5

Come altri hanno sottolineato, hai un'eccezione non rilevata, che chiama terminate(). È definito dall'implementazione (si veda lo Standard, 15.3 paragrafo 9 e 15.5.1 paragrafo 2) se in questo caso vengono chiamati i distruttori, e apparentemente la definizione è "No, non lo faranno". (Se viene chiamato terminate() per qualsiasi altra ragione che lanciare un'eccezione che non ha un gestore, i distruttori non verranno chiamati.)

4

Gli oggetti A non vengono distrutti perché std :: terminate viene chiamato.

std :: terminate viene chiamato quando un'eccezione non gestita fuoriuscisce dal principale. Se avvolgi il tuo codice in un try/catch (anche se il catch ha appena rilanciato) vedrai il comportamento che ti aspettavi.

6

Se un'eccezione sfugge a main() è tempo definito dall'implementazione, lo stack viene svolto.

provare

int main() 
{ 
    try 
    { 
     doWork(); // Do you experiment here. 
    } 
    catch(...) 
    { /* 
     * By catching here you force the stack to unwind correctly. 
     */ 
     throw; // re-throw so exceptions pass to the OS for debugging. 
    } 
} 
0

Dato che l'eccezione non è gestita dal momento in cui raggiunge main(), si traduce in una chiamata a std :: terminate(), si ha in sostanza l'equivalente di

int main(void) { 
    A a1(1); 
    A a2(2); 
    exit(1); 
} 

I distruttori NON sono garantiti per essere chiamati nei casi in cui il programma termina prima che escano dal campo di applicazione. Per un altro buco nel Raii, considerare:

int main(void) { 
    A *a1 = new A(1); 
} 
+1

Il secondo esempio non è un buco in RAII; è un esempio di non usare RAII. –

1

Altri hanno suggerito di mettere un try/catch all'interno main() per gestire questa situazione, che funziona bene. Per qualche ragione, trovo che il 'function-try-block' usato raramente abbia un aspetto migliore, il che mi sorprende (pensavo che sarebbe sembrato troppo strano). Ma non credo che ci sia alcun reale vantaggio:

int main(void) 
try 
{ 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
    return 0; 
} 
catch (...) 
{ 
    throw; 
} 

Un paio di svantaggi sono che dal momento che è raramente utilizzato un sacco di sviluppatori avere buttato per un ciclo quando lo vedono, e VC6 soffoca su di esso, se questo è un considerazione.

0

Il seguente codice funziona.

#include <exception> 
#include <iostream> 

class A { 
public: 
    A(int i) { i_ = i; std::cout << "A " << i_ << " constructed" << std::endl; } 
    ~A() { std::cout << "A " << i_ << " destructed" << std::endl; } 
private: 
    int i_; 
}; 

void test() { 
    A a1(1); 
    A a2(2); 
    throw std::exception(); 
} 

int main(void) { 
try { 
    test(); 
} catch(...) { 
} 
    return 0; 
}