Many people are no doubt familiar with Mr. Alexandrescus ScopeGuard template (now part of Loki) and the new version ScopeGuard11 presented here: http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C
with source here: https://gist.github.com/KindDragon/4650442
In his talk at c++ and beyond 2012 he mentioned that he couldn't find a way to correctly detect if scope was being exited because of an exception. Therefore he couldn't implement a SCOPE_FAIL macro which would execute the supplied lambda (usually used for roll back code) if and only if the scope exited because of an exception. This would render the dismiss() member function unneeded and make code more readable.
Since I am by no means as genius or experienced as Mr. Alexandrescu I expect implementing SCOPE_FAIL is not as easy as this:
~ScopeGuard11(){ //destructor
if(std::uncaught_exception()){ //if we are exiting because of an exception
f_(); //execute the functor
}
//otherwise do nothing
}
My question is why not?
With a ScopeGuard11
class that has your destructor, the member f_
may be called, even if it is not the current scope (that is supposed to be protected by the guard) that is being exited due to an exception. Use of this guard is unsafe in code that might be used during exception cleanup.
Try this example:
#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;
}
You'd want to see one rollback
when leaving baz
, but you'll see two - including a spurious one from leaving foo
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With