Blog coding and discussion of coding about JavaScript, PHP, CGI, general web building etc.

Thursday, March 24, 2016

The simplest and neatest c++11 ScopeGuard

The simplest and neatest c++11 ScopeGuard


I'm attempting to write a simple ScopeGuard based on Alexandrescu concepts but with c++11 idioms.

namespace RAII  {      template< typename Lambda >      class ScopeGuard      {          mutable bool committed;          Lambda rollbackLambda;           public:                ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}                template< typename AdquireLambda >              ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)              {                  _al();              }                ~ScopeGuard()              {                  if (!committed)                      rollbackLambda();              }              inline void commit() const { committed = true; }      };        template< typename aLambda , typename rLambda>      const ScopeGuard< rLambda >& makeScopeGuard( const aLambda& _a , const rLambda& _r)      {          return ScopeGuard< rLambda >( _a , _r );      }        template      const ScopeGuard< rLambda >& makeScopeGuard(const rLambda& _r)      {          return ScopeGuard< rLambda >(_r );      }  }  

Here is the usage:

void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptions()   {     std::vector myVec;     std::vector someOtherVec;       myVec.push_back(5);     //first constructor, adquire happens elsewhere     const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );         //sintactically neater, since everything happens in a single line     const auto& b = RAII::makeScopeGuard( [&]() { someOtherVec.push_back(42); }                       , [&]() { someOtherVec.pop_back(); } );        b.commit();     a.commit();  }  

Since my version is way shorter than most examples out there (like Boost ScopeExit) i'm wondering what specialties i'm leaving out. Hopefully i'm in a 80/20 scenario here (where i got 80 percent of neatness with 20 percent of lines of code), but i couldn't help but wonder if i'm missing something important, or is there some shortcoming worth mentioning of this version of the ScopeGuard idiom

thanks!

Edit I noticed a very important issue with the makeScopeGuard that takes the adquire lambda in the constructor. If the adquire lambda throws, then the release lambda is never called, because the scope guard was never fully constructed. In many cases, this is the desired behavior, but i feel that sometimes a version that will invoke rollback if a throw happens is desired as well:

//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..  template< typename aLambda , typename rLambda>  ScopeGuard< rLambda > // return by value is the preferred C++11 way.  makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding  {      return ScopeGuard< rLambda >( std::forward(_a) , std::forward(_r )); // *** no longer UB, because we're returning by value  }    template< typename aLambda , typename rLambda>  ScopeGuard< rLambda > // return by value is the preferred C++11 way.  makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding  {      auto scope = ScopeGuard< rLambda >(std::forward(_r )); // *** no longer UB, because we're returning by value      _a();      return scope;  }  

so for completeness, i want to put in here the complete code, including tests:


#include     namespace RAII  {        template< typename Lambda >      class ScopeGuard      {          bool committed;          Lambda rollbackLambda;           public:                ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}                ScopeGuard( const ScopeGuard& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda)               {                  if (_sc.committed)                     committed = true;                  else                     _sc.commit();              }                ScopeGuard( ScopeGuard&& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda)              {                  if (_sc.committed)                     committed = true;                  else                     _sc.commit();              }                //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..              template< typename AdquireLambda >              ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)              {                 std::forward(_al)();              }                //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..              template< typename AdquireLambda, typename L >              ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward(_l))              {                  std::forward(_al)(); // just in case the functor has &&-qualified operator()              }                  ~ScopeGuard()              {                  if (!committed)                      rollbackLambda();              }              inline void commit() { committed = true; }      };          //WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..      template< typename aLambda , typename rLambda>      ScopeGuard< rLambda > // return by value is the preferred C++11 way.      makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding      {          return ScopeGuard< rLambda >( std::forward(_a) , std::forward(_r )); // *** no longer UB, because we're returning by value      }        template< typename aLambda , typename rLambda>      ScopeGuard< rLambda > // return by value is the preferred C++11 way.      makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding      {          auto scope = ScopeGuard< rLambda >(std::forward(_r )); // *** no longer UB, because we're returning by value          _a();          return scope;      }        template      ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)      {          return ScopeGuard< rLambda >( std::forward(_r ));      }        namespace basic_usage      {          struct Test          {                std::vector myVec;              std::vector someOtherVec;              bool shouldThrow;              void run()              {                  shouldThrow = true;                  try                  {                      SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();                  } catch (...)                  {                      AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");                  }                  shouldThrow = false;                  SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();                  AssertMsg( myVec.size() == 1 && someOtherVec.size() == 1 , "unexpected end state");                  shouldThrow = true;                  myVec.clear(); someOtherVec.clear();                    try                  {                      SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows();                  } catch (...)                  {                      AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");                  }              }                void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows() //throw()              {                    myVec.push_back(42);                  auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );                      auto b = RAII::makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); }                                      , [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );                    if (shouldThrow) throw 1;                     b.commit();                  a.commit();              }                void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows() //throw()              {                  myVec.push_back(42);                  auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );                      auto b = RAII::makeScopeGuardThatDoesRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); if (shouldThrow) throw 1; }                                      , [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );                    b.commit();                  a.commit();              }          };      }  }  

Answer by mirk for The

simplest and neatest c++11 ScopeGuard

makeScopeGuard returns a const reference. You can't store this const reference in a const ref at the caller's side in a line like:

const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );   

So you are invoking undefined behaviour.

Herb Sutter GOTW 88 gives some background about storing values in const references.

Answer by R. Martinho Fernandes for The simplest and neatest c++11 ScopeGuard


Boost.ScopeExit is a macro that needs to work with non-C++11 code, i.e. code that has no access to lambdas in the language. It uses some clever template hacks (like abusing the ambiguity that arises from using < for both templates and comparison operators!) and the preprocessor to emulate lambda features. That's why the code is longer.

The code shown is also buggy (which is probably the strongest reason to use an existing solution): it invokes undefined behaviour due to returning references to temporaries.

Since you're trying to use C++11 features, the code could be improved a lot by using move semantics, rvalue references and perfect-forwarding:

template< typename Lambda >  class ScopeGuard  {      bool committed; // not mutable      Lambda rollbackLambda;       public:              // make sure this is not a copy ctor          template >, ScopeGuard>> =_          >          /* see http://loungecpp.net/w/EnableIf_in_C%2B%2B11           * and http://stackoverflow.com/q/10180552/46642 for info on DisableIf           */          explicit ScopeGuard(L&& _l)          // explicit, unless you want implicit conversions from *everything*          : committed(false)          , rollbackLambda(std::forward(_l)) // avoid copying unless necessary          {}            template< typename AdquireLambda, typename L >          ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward(_l))          {              std::forward(_al)(); // just in case the functor has &&-qualified operator()          }            // move constructor          ScopeGuard(ScopeGuard&& that)          : committed(that.committed)          , rollbackLambda(std::move(that.rollbackLambda)) {              that.committed = true;          }            ~ScopeGuard()          {              if (!committed)                  rollbackLambda(); // what if this throws?          }          void commit() { committed = true; } // no need for const  };    template< typename aLambda , typename rLambda>  ScopeGuard< rLambda > // return by value is the preferred C++11 way.  makeScopeGuard( aLambda&& _a , rLambda&& _r) // again perfect forwarding  {      return ScopeGuard< rLambda >( std::forward(_a) , std::forward(_r )); // *** no longer UB, because we're returning by value  }    template  ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)  {      return ScopeGuard< rLambda >( std::forward(_r ));  }  

Answer by Andr for The simplest and neatest c++11 ScopeGuard


You might be interested in seeing this presentation by Andrei himself on his own taken on how to improve scopedguard with c++11

Answer by CTMacUser for The simplest and neatest c++11 ScopeGuard


You already chosen an answer, but I'll take the challenge anyway:

#include   #include   #include     template < typename RollbackLambda >  class ScopeGuard;    template < typename RollbackLambda >  auto  make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard::type>;    template < typename RollbackLambda >  class ScopeGuard  {      // The input may have any of: cv-qualifiers, l-value reference, or both;      // so I don't do an exact template match.  I want the return to be just      // "ScopeGuard," but I can't figure it out right now, so I'll make every      // version a friend.      template < typename AnyRollbackLambda >      friend      auto make_ScopeGuard( AnyRollbackLambda && ) -> ScopeGuard::type>;    public:      using lambda_type = RollbackLambda;    private:      // Keep the lambda, of course, and if you really need it at the end      bool        committed;      lambda_type  rollback;        // Keep the main constructor private so regular creation goes through the      // external function.      explicit  ScopeGuard( lambda_type rollback_action )          : committed{ false }, rollback{ std::move(rollback_action) }      {}    public:      // Do allow moves      ScopeGuard( ScopeGuard &&that )          : committed{ that.committed }, rollback{ std::move(that.rollback) }      { that.committed = true; }      ScopeGuard( ScopeGuard const & ) = delete;        // Cancel the roll-back from being called.      void  commit()  { committed = true; }        // The magic happens in the destructor.      // (Too bad that there's still no way, AFAIK, to reliably check if you're      // already in exception-caused stack unwinding.  For now, we just hope the      // roll-back doesn't throw.)      ~ScopeGuard()  { if (not committed) rollback(); }  };    template < typename RollbackLambda >  auto  make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard::type>  {      using std::forward;        return ScopeGuard::type>{       forward(r) };  }    template < typename ActionLambda, typename RollbackLambda >  auto  make_ScopeGuard( ActionLambda && a, RollbackLambda &&r, bool   roll_back_if_action_throws ) -> ScopeGuard::type>  {      using std::forward;        if ( not roll_back_if_action_throws )  forward(a)();      auto  result = make_ScopeGuard( forward(r) );      if ( roll_back_if_action_throws )  forward(a)();      return result;  }    int  main()  {      auto aa = make_ScopeGuard( []{std::cout << "Woah" << '\n';} );      int  b = 1;        try {       auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, true );      } catch (...) {}      std::cout << b++ << '\n';      try {       auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, false );      } catch (...) {}      std::cout << b++ << '\n';        return 0;  }  // Should write: "0", "2", and "Woah" in that order on separate lines.  

Instead of having creation functions and a constructor, you limited to just the creation functions, with the main constructor being private. I couldn't figure out how to limit the friend-ed instantiations to just the ones involving the current template parameter. (Maybe because the parameter is mentioned only in the return type.) Maybe a fix to that can be asked on this site. Since the first action doesn't need to be stored, it's present only in the creation functions. There's a Boolean parameter to flag if throwing from the first action triggers a roll-back or not.

The std::decay part strips both cv-qualifiers and reference markers. But you can't use it for this general purpose if the input type is a built-in array, since it'll apply the array-to-pointer conversion too.

Answer by Fozi for The simplest and neatest c++11 ScopeGuard


Even shorter: I don't know why you guys insist on putting the template on the guard class.

#include     class scope_guard {  public:       template       scope_guard(Callable && undo_func) : f(std::forward(undo_func)) {}        scope_guard(scope_guard && other) : f(std::move(other.f)) {          other.f = nullptr;      }        ~scope_guard() {          if(f) f(); // must not throw      }        void dismiss() throw() {          f = nullptr;      }        scope_guard(const scope_guard&) = delete;      void operator = (const scope_guard&) = delete;    private:      std::function f;  };  

Note that it is essential that the cleanup code does not throw, otherwise you get in similar situations as with throwing destructors.

Usage:

// do step 1  step1();  scope_guard guard1 = [&]() {      // revert step 1      revert1();  };    // step 2  step2();  guard1.dismiss();  

My inspiration was the same DrDobbs article as for the OP.

Answer by Erik van Velzen for The simplest and neatest c++11 ScopeGuard


There's a chance this approach will be standardized in C++17 or in the Library Fundamentals TS through proposal P0052R0

template   scope_exit make_scope_exit(EF &&exit_function) noexcept;    template   scope_exit make_scope_fail(EF && exit_function) noexcept;    template   scope_exit make_scope_success(EF && exit_function) noexcept;  

On first glance this has the same caveat as std::async because you have to store the return value or the destructor will be called immediately and it won't work as expected.


Fatal error: Call to a member function getElementsByTagName() on a non-object in D:\XAMPP INSTALLASTION\xampp\htdocs\endunpratama9i\www-stackoverflow-info-proses.php on line 72

Related Posts:

0 comments:

Post a Comment

Popular Posts

Fun Page

Powered by Blogger.