Why can't Alexandrescu use std::uncaught_excep

2019-04-18 10:06发布

问题:

This question already has an answer here:

  • Scope(failure) in C++11? 2 answers

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?

回答1:

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.