2013-02-17 10 views
16

Molte persone sono senza dubbio familiarità con il modello Mr. Alexandrescus ScopeGuard (ora parte di Loki) e la nuova versione ScopeGuard11 qui presentata: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-CPerché Alexandrescu non può utilizzare std :: uncaught_exception() per implementare SCOPE_FAIL in ScopeGuard11?

con fonte qui: https://gist.github.com/KindDragon/4650442

Nel suo discorso in C++ e oltre il 2012 ha menzionato che non è stato in grado di trovare un modo per rilevare correttamente se l'oscillazione è stata interrotta a causa di un'eccezione. Pertanto non è stato in grado di implementare una macro SCOPE_FAIL che eseguisse il lambda fornito (di solito utilizzato per il codice di rollback) se e solo se l'ambito è stato chiuso a causa di un'eccezione. Ciò renderebbe non necessaria la funzione membro dismiss() e renderebbe il codice più leggibile.

Dato che io sono in alcun modo come genio o vissuta come Mr. Alexandrescu mi aspetto che implementando SCOPE_FAIL non è così facile come questo:

~ScopeGuard11(){      //destructor 
    if(std::uncaught_exception()){ //if we are exiting because of an exception 
     f_();       //execute the functor 
    } 
    //otherwise do nothing 
} 

mia domanda è perché no?

+0

È strano, qualcosa mi dice che dovrebbe funzionare, ma se provo, 'uncaught_exception()' restituisce sempre 'false'. –

+0

Ricordo vagamente Herb Sutter che ha qualcosa del genere su GotW quando torna, ma non riesco a trovarlo più. Forse Alzheimer;) o non sto googleing la cosa giusta. – odinthenerd

+0

Penso che nel caso di protezione dell'ambito, si possa effettivamente usare 'std :: uncaught_exception', poiché la guardia dell'ambito non sarà mai un membro di un'altra classe (e sicuramente non una variabile locale in un distruttore di classe). – Xeo

risposta

11

Con una classe ScopeGuard11 con il distruttore, è possibile chiamare il membro f_, anche se non è l'ambito corrente (che dovrebbe essere protetto dalla guardia) che viene chiuso a causa di un'eccezione. L'uso di questa protezione non è sicuro nel codice che potrebbe essere utilizzato durante la pulizia delle eccezioni.

Prova questo esempio:

#include <exception> 
#include <iostream> 
#include <string> 

// simplified ScopeGuard11 
template <class Fun> 
struct ScopeGuard11 { 
    Fun f_; 
    ScopeGuard11(Fun f) : f_(f) {} 
    ~ScopeGuard11(){      //destructor 
     if(std::uncaught_exception()){ //if we are exiting because of an exception 
      f_();       //execute the functor 
     } 
     //otherwise do nothing 
     } 
}; 

void rollback() { 
    std::cout << "Rolling back everything\n"; 
} 
void could_throw(bool doit) { 
    if (doit) throw std::string("Too bad"); 
} 

void foo() { 
    ScopeGuard11<void (*)()> rollback_on_exception(rollback); 
    could_throw(false); 
    // should never see a rollback here 
    // as could throw won't throw with this argument 
    // in reality there might sometimes be exceptions 
    // but here we care about the case where there is none 
} 

struct Bar { 
    ~Bar() { 
     // to cleanup is to foo 
     // and never throw from d'tor 
     try { foo(); } catch (...) {} 
    } 
}; 

void baz() { 
    Bar bar; 
    ScopeGuard11<void (*)()> more_rollback_on_exception(rollback); 
    could_throw(true); 
} 

int main() try { 
    baz(); 
} catch (std::string & e) { 
    std::cout << "caught: " << e << std::endl; 
} 

Che ci si vuole vedere uno rollback al momento di lasciare baz, ma vedrete due - tra cui uno spurio di lasciare foo.

+1

Interessante. Mi chiedo però se non è una questione di buon design per evitare questo schema. Qui, 'foo()' non getterà mai (altrimenti sarebbe male chiamarlo da un distruttore). Ma se non lo farà mai, perché usare un rollback sull'eccezione? –

+0

Quel 'foo()' è stato impostato per non gettare mai, era per motivi di dimostrazione. In un vero caso problematico, le condizioni in cui 'could_throw' potrebbe lanciare (sic!) Sarebbe meno ovvia. In tal caso il distruttore 'Bar' potrebbe fare' try {pippo(); } catch (...) {} '. Oppure potrebbero esserci più livelli intermedi, facendo attenzione che qualsiasi eccezione in 'pippo()' non lascerebbe il distruttore 'Bar'. Tuttavia otterresti un rollback spurio in 'foo()' anche se viene lasciato con un ritorno regolare. – JoergB

+0

@AndyProwl: ho modificato l'esempio per renderlo più chiaro. – JoergB