Search code examples
c++returnraiiexception-safety

Is there a C++ standard class to set a variable to a value at scope exit


Within the scope of a member function, I want to temporarly set a member variable to a certain value.

Then, when this function returns, I want to reset this member variable to a given known value.

To bo safe against exceptions and multiple returns, and I've done it with a simple RAII like class. It's defined within the scope of the member function.

void MyClass::MyMemberFunction() {
    struct SetBackToFalse {
        SetBackToFalse(bool* p): m_p(p) {}
        ~SetBackToFalse() {*m_p=false;}
    private:
        bool* m_p;
    };

    m_theVariableToChange = true;
    SetBackToFalse resetFalse( &m_theVariableToChange ); // Will reset the variable to false.

    // Function body that may throw.
}

It seems so obviously commonplace, that I was wondering if there was any such template class doing this in the C++ standard library?


Solution

  • Not yet (there have been proposals for this). But implementing a generic one is simple enough;

    struct scope_exit {
      std::function<void()> f_;
      explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
      ~scope_exit() { if (f_) f_(); }
    };
    // ...
    m_theVariableToChange = true;
    scope_exit resetFalse([&m_theVariableToChange]() { m_theVariableToChange = false; });
    

    For simplicity above, I've redacted the copy and move constructors etc...

    Marking them as = delete will make the above a minimal solution. Further; moving could be allowed if desired, but copying should be prohibited.


    A more complete scope_exit would look like (online demo here);

    template <typename F>
    struct scope_exit {
      F f_;
      bool run_;
      explicit scope_exit(F f) noexcept : f_(std::move(f)), run_(true) {}
      scope_exit(scope_exit&& rhs) noexcept : f_((rhs.run_ = false, std::move(rhs.f_))), run_(true) {}
      ~scope_exit()
      {
        if (run_)
          f_(); // RAII semantics apply, expected not to throw
      }
    
      // "in place" construction expected, no default ctor provided either
      // also unclear what should be done with the old functor, should it
      // be called since it is no longer needed, or not since *this is not
      // going out of scope just yet...
      scope_exit& operator=(scope_exit&& rhs) = delete;
      // to be explicit...
      scope_exit(scope_exit const&) = delete;
      scope_exit& operator=(scope_exit const&) = delete;
    };
    
    template <typename F>
    scope_exit<F> make_scope_exit(F&& f) noexcept
    {
      return scope_exit<F>{ std::forward<F>(f) };
    }
    

    Notes on the implementation;

    • std::function<void()> can be used to erase the type of the functor. std::function<void()> offers exception guarantees on the move constructors based on the exception specific of the held function. A sample of this implementation is found here
    • These exception specifications are consistent the C++ proposal and GSL implementations
    • I've redacted most of the motivation for the noexcept, more substantial detail is found in the C++ proposal
    • The "usual" RAII semantics of the destructor, hence the "scope exit" function is applicable; it will not throw, this is also consistent with the C++11 specification on the default exception specification for a destructor. See cppreference, SO Q&A, GotW#47 and HIC++

    Other implementations can be found;