Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is ScopeGuard in C++?

Tags:

c++

I am under the impression that it is a C++ class contained in a library written by a third party. I tried searching on Google, and I found one post that said it was a good idea to use it. However, it failed to describe exactly what it is and how I can incorporate it into my code. Thanks.

like image 476
beatleman Avatar asked Jul 12 '15 06:07

beatleman


2 Answers

ScopeGuard was once a particular implementation of scope guards by Petru Marginean and Andrei Alexandrescu. The idea is to let the destructor of a guard object call a user specified cleanup action at the end of a scope (read: block), unless the scope guard is dismissed. Marginean came up with an ingenious idea of declaring a scope guard object for C++03, based on lifetime extension of a reference to const.

Today “scope guard” is more the general idea.

Scope guards are based on RAII (automatic destructor calls used for cleanup), just as e.g. a for loop is based on jumps, but one wouldn't ordinarly call a for loop a jump-based piece of code, because that loses most of the information of what it is about, and likewise one does not ordinarily refer to scope guards as RAII. for loops are at a higher level of abstraction, and are a more specialized concept, than jumps. Scope guards are at a higher level of abstraction, and are a more specialized concept, than RAII.


In C++11 scope guards can be trivially implemented in terms of std::function, with the cleanup action supplied in each place via a lambda expression.

Example:

#include <functional>       // std::function
#include <utility>          // std::move

namespace my {
    using std::function;
    using std::move;

    class Non_copyable
    {
    private:
        auto operator=( Non_copyable const& ) -> Non_copyable& = delete;
        Non_copyable( Non_copyable const& ) = delete;
    public:
        auto operator=( Non_copyable&& ) -> Non_copyable& = default;
        Non_copyable() = default;
        Non_copyable( Non_copyable&& ) = default;
    };

    class Scope_guard
        : public Non_copyable
    {
    private:
        function<void()>    cleanup_;

    public:
        friend
        void dismiss( Scope_guard& g ) { g.cleanup_ = []{}; }

        ~Scope_guard() { cleanup_(); }

        template< class Func >
        Scope_guard( Func const& cleanup )
            : cleanup_( cleanup )
        {}

        Scope_guard( Scope_guard&& other )
            : cleanup_( move( other.cleanup_ ) )
        { dismiss( other ); }
    };

}  // namespace my

#include <iostream>
void foo() {}
auto main() -> int
{
    using namespace std;
    my::Scope_guard const final_action = []{ wclog << "Finished! (Exit from main.)\n"; };

    wcout << "The answer is probably " << 6*7 << ".\n";
}

The rôle of the function here is to avoid templating so that Scope_guard instances can be declared as such, and passed around. An alternative, slightly more complex and with slightly constrained usage, but possibly marginally more efficient, is to have a class templated on a functor type, and use C++11 auto for declarations, with the scope guard instance created by a factory function. Both these techniques are simple C++11 ways to do what Marginean did with reference lifetime extension for C++03.

like image 101
Cheers and hth. - Alf Avatar answered Nov 15 '22 23:11

Cheers and hth. - Alf


It's more of a design pattern than a particular class. It is a way of aquiring/releasing resources (such as files, memory, or mutexes) that is exception safe. unique_lock in c++11 follows this pattern.

For example, with unique_lock, instead of writing code like this:

void foo()
{
    myMutex.lock();
    bar();
    myMutex.unlock();
}

You write code like this:

void foo()
{
    unique_lock<mutex> ulock(myMutex);
    bar();
}

In the first case, what if bar throws an exception? Then, myMutex will never be unlocked, and your program would be left in an invalid state. In the second case, however, unique_lock is programmed to lock the mutex in its constructor, and unlock it in its destructor. Even if bar throws an exception, the unique_lock will be destructed as the stack unwinds when the exception travels upward, and so the lock will be released. This saves you having to wrap every call to bar in a try/catch block and handle exceptions manually.

like image 39
IanPudney Avatar answered Nov 15 '22 22:11

IanPudney