2013-02-17 37 views
16

Wielu ludzi jest bez wątpienia zna Pan Alexandrescus ScopeGuard szablonu (obecnie część Loki) oraz nową wersję ScopeGuard11 przedstawiony tutaj: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-CDlaczego program Alexandrescu nie może użyć std :: uncaught_exception() do zaimplementowania SCOPE_FAIL w ScopeGuard11?

ze źródłem tutaj: https://gist.github.com/KindDragon/4650442

W rozmowie w C++ i poza 2012 r. wspomniał, że nie może znaleźć sposobu na prawidłowe wykrycie, czy zakres został opuszczony z powodu wyjątku. Dlatego nie mógł zaimplementować makra SCOPE_FAIL, które wykonałoby dostarczoną lambdę (zwykle używaną do wycofania kodu) wtedy i tylko wtedy, gdy zakres został wyłączony z powodu wyjątku. Spowoduje to uczynienie funkcji członkowca dismiss() niepotrzebną i sprawi, że kod stanie się bardziej czytelny.

Ponieważ jestem w żaden sposób jako geniusza lub doświadczył jako Mr. Alexandrescu Spodziewam realizacji SCOPE_FAIL nie jest tak łatwe, jak to:

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

Moje pytanie brzmi, dlaczego nie?

+0

To dziwne, coś mi mówi, że to powinno działać, ale jeśli spróbuję, 'uncaught_exception()' zawsze zwraca 'false'. –

+0

Niejasno pamiętam, że Herb Sutter miał coś takiego w GotW drodze powrotnej, ale nie mogę jej już znaleźć. Może Alzheimer;) lub nie robię niczego dobrego. – odinthenerd

+0

Myślę, że w przypadku straży granicznej można w rzeczywistości użyć 'std :: uncaught_exception', ponieważ osłona oscyloskopu nigdy nie będzie członkiem innej klasy (az pewnością nie jest zmienną lokalną w klasie 'destructor). – Xeo

Odpowiedz

11

Z klasą ScopeGuard11, która ma swój destruktor, można wywołać członka f_, nawet jeśli nie jest to bieżący zakres (który ma być chroniony przez strażnika), który jest zamykany z powodu wyjątku. Używanie tego strażnika jest niebezpieczne w kodzie, który może być użyty podczas czyszczenia wyjątków.

Spróbuj przykład:

#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; 
} 

że chcesz zobaczyć jeden rollback przy opuszczaniu baz, ale zobaczysz dwa - w tym jeden z fałszywych pozostawiając foo.

+1

Interesujące. Zastanawiam się jednak, czy to nie jest kwestia dobrego projektu, aby uniknąć tego wzorca. W tym przypadku 'foo()' nigdy nie wyrzuci (w przeciwnym razie źle byłoby wywołać go z destruktora). Ale jeśli nigdy nie rzuci, dlaczego używa wycofania na wyjątku? –

+0

To 'foo()' zostało ustawione tak, by nigdy nie rzucać, było ze względu na demonstrację. W prawdziwym przypadku problemu, warunki, w których 'could_throw' może rzucić (sic!) Byłyby mniej oczywiste. W takim przypadku destruktor 'Bar' mógłby zrobić' try {foo(); } catch (...) {} '. Lub może istnieć więcej warstw pomiędzy nimi, dbając o to, aby każdy wyjątek w 'foo()' nie opuszczał destruktora 'Bar'. Niemniej jednak otrzymasz fałszywe wycofanie w 'foo()' nawet jeśli zostanie ono pozostawione przez regularny powrót. – JoergB

+0

@AndyProwl: Poprawiłem przykład, aby było to jaśniejsze. – JoergB