27

I've written a small utility class for C++11 which I use as a scope guard for easier handling of exception safety and similar things.

Seems somewhat like a hack. But I'm suprised I haven't seen it somewhere else using C++11 features. I think boost has something similar for C++98.

But is it a good idea? Or are there potential problems I have missed? Is there already a similar solution (with C++11 features) in boost or similar?

    namespace detail 
    {
        template<typename T>
        class scope_exit : boost::noncopyable
        {
        public:         
            explicit scope_exit(T&& exitScope) : exitScope_(std::forward<T>(exitScope)){}
            ~scope_exit(){try{exitScope_();}catch(...){}}
        private:
            T exitScope_;
        };          

        template <typename T>
        scope_exit<T> create_scope_exit(T&& exitScope)
        {
            return scope_exit<T>(std::forward<T>(exitScope));
        }
    }


#define _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line) name##line
#define _UTILITY_EXIT_SCOPE_LINENAME(name, line) _UTILITY_EXIT_SCOPE_LINENAME_CAT(name, line)
#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit(f)

and it's used something like.

int main () 
{
  ofstream myfile;
  myfile.open ("example.txt");
  UTILITY_SCOPE_EXIT([&]{myfile.close();}); // Make sure to close file even in case of exception
  myfile << "Writing this to a file.\n"; // Imagine this could throw
  return 0;
}
Xeo
  • 129,499
  • 52
  • 291
  • 397
ronag
  • 49,529
  • 25
  • 126
  • 221
  • see http://pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x-style/ – sellibitze Sep 10 '10 at 08:32
  • 2
    You may want to have a look at my lazy-RAII classes: http://stackoverflow.com/questions/2419650/c-c-macro-template-blackmagic-to-generate-unique-name/2419715#2419715 . Note that your `scope_exit` relies on copy constructor elimination. If you compile this snippet without that optimization, you call the scope-exit lambda twice. See my RAII classes on how to work-around that problem. – Johannes Schaub - litb Sep 29 '10 at 20:03
  • 2
    I think myfile will be closed when it goes out of scope. It should already be exception safe. – emsr Jul 08 '11 at 16:52
  • Here is a simple C++11 scope guard implementation that doesn't depend on copy constructor elimination and doesn't risk dangling pointers to temporaries: http://stackoverflow.com/a/12545195/558823 – Stephan Tolksdorf Sep 22 '12 at 15:53
  • Another potential problem is your use of reserved names for your macros... – Zaxter Sep 04 '17 at 13:50
  • `std::experimental::scope_exit` – CygnusX1 Jun 30 '22 at 19:01

11 Answers11

22

But is it a good idea?

Sure. A related topic is the RAII paradigm.

Or are there potential problems I have missed?

You don't handle exceptions.

Is there already a similar solution (with C++0x features) in boost or similar?

Alexandrescu came up with ScopeGuard a long time back. Both Boost and std::tr1 has a thing called scoped_ptr and shared_ptr (with a custom deleter) that allows you to accomplish just this.

dirkgently
  • 108,024
  • 16
  • 131
  • 187
20

For the record, there is Boost ScopeExit.

Gregory Pakosz
  • 69,011
  • 20
  • 139
  • 164
9

Scope guards are definitely a good idea. I think the scope guard concept is potent tool for exception safety. If you can make a safer, cleaner version that Boost's ScopeExit using C++0x syntax, I think it would be well worth your time.

Similar to Alexandrescu's ScopeGuard and Boost's ScopeExit , the D programming language has direct syntax for this sort of thing. The D programming team thought the scope guard was a good enough idea that they added it directly to the language (ie it's not implemented in a library).

Example.

void foo( bool fail )
{
   scope(exit)
   {
      writeln("I'm always printed");
   }

   scope(success) writeln("The function exited normally");

   scope(error)
      writeln("The function exited with an exception.");

   if( fail )
      throw new Exception("Die Die Die!");
}

The scope based guards aren't anything new. It's functionality can easily be replicated with a class destructor (RAII and all that). It's also possible to replace with try/finally in C# or Java. Heck, even pthreads provides a rudimentary scope guard, called pthread_cleanup_push.

What makes scope guards so powerful is when you have multiple scope(*) statements in the function. It scales incredibly well, as opposed to try/finally which require super human powers to manage anything more than two.

deft_code
  • 57,255
  • 29
  • 141
  • 224
5

If replace create_scope_exit by a binary operator, we can remove parentheses:

class at_scope_exit
{
    template<typename F>
    struct scope_exit_fn_holder : boost::noncopyable
    {
        scope_exit_fn_holder(F&& f) : f(std::forward<F>(f)) {}

        F f;
        ~scope_exit_fn_holder() { f(); }
    };

    template<typename F>
    friend scope_exit_fn_holder<F> operator==(at_scope_exit, F&& f)
    {
        return scope_exit_fn_holder<F>(std::forward<F>(f));
    }
};

Usage:

auto atScopeExit = at_scope_exit() == [&]
{
    ...
};

upd:
Corresponding macro:

#include <boost/preprocessor/cat.hpp>

#define AT_SCOPE_EXIT auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [&]
#define AT_SCOPE_EXIT_EX(...) auto BOOST_PP_CAT(scopeExit_, __LINE__) = at_scope_exit() == [__VA_ARGS__]
Abyx
  • 12,345
  • 5
  • 44
  • 76
  • The problem with this version is that you have to come up with a unique name for each "at_scope_exit". – ronag Jun 14 '11 at 19:07
  • @ronag do you mean "for each atScopeExit"? You can use a macro with __LINE__ like in your code. – Abyx Jun 15 '11 at 06:58
4

For the record, there is scope_exit in the TS 3

Jaffa
  • 12,442
  • 4
  • 49
  • 101
2

Currently using this solution:

struct _tag_defer {
    std::function<void()> fn;
    _tag_defer() = default;
    ~_tag_defer() { fn(); }
    void operator<<(std::function<void()> f) { fn = f; }
};
// clang-format off
#define CONCAT(a, b) CONCAT_INNER(a, b)
#define CONCAT_INNER(a, b) a ## b
#define defer_name CONCAT(__defer, __LINE__)
#define defer _tag_defer defer_name; defer_name << [&]
// clang-format on

Use it like:

{
    defer { last_code_to_execute_on_scope_exit(); };
    ...
    defer { first_code_to_execute_on_scope_exit(); };
}
Robert
  • 2,330
  • 29
  • 47
0

We could omit the ugly [&] stuff by putting it in the define:

#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit([&]f)

Then:

UTILITY_SCOPE_EXIT({myfile.close();});

Tested with MSVC++ 11.0 (VS2012). Regards.

bobef
  • 990
  • 1
  • 9
  • 14
0

This is a good idea, but there are a couple of problems with you class.

  1. you should disable the new operator (you don't want to need the user to use it in such a way that forces to call delete on this, right?)
  2. you need a "commit" function, in order for this to be a scope guard instead of a simple RAII

notice that if you implement point 2 you need a meaningful name for each scopeguard you instantiate. This is, in general, not a problem, but it could be in your application (or to your taste).

Finally, this question would probably have been more appropriate for CodeReview.

Community
  • 1
  • 1
Stefano Falasca
  • 8,837
  • 2
  • 18
  • 24
0

Using Boost:

#include <boost/preprocessor/cat.hpp>

template<class Fn>
class ScopeGuardDetails {
    const Fn m_fn;
public:
    constexpr ScopeGuardDetails(Fn &&fn) : m_fn(fn) {}
    ~ScopeGuardDetails() { m_fn(); }
};
#define ScopeGuardName BOOST_PP_CAT(BOOST_PP_CAT(__scope_guard, _), BOOST_PP_CAT(BOOST_PP_CAT(__LINE__, _), __COUNTER__))
#define defer(stmt) const auto ScopeGuardName = [](const auto _fn) { \
    return ScopeGuardDetails<decltype(_fn)> { std::move(_fn) }; \
}([&] { stmt });

Usage:

if (gdiplus::GdiplusStartup(&token, &startupInput, nullptr) == Gdiplus::Ok) {
    defer({
        gdiplus::GdiplusShutdown(token);
    });
    ...
}
Steve Fan
  • 3,019
  • 3
  • 19
  • 29
0

The implementation could be very much simplified using tr1::function and tr1::unique_ptr, as below:

namespace detail
{
    class ScopeGuard
    {
    public:
        explicit ScopeGuard(std::function<void()> onExitScope) 
            : onExitScope_(onExitScope), dismissed_(false)
        { }

        ~ScopeGuard()
        {
            try
            {
                if(!dismissed_)
                {
                    onExitScope_();
                }
            }
            catch(...){}
        }

        void Dismiss()
        {
            dismissed_ = true;
        }
    private:
        std::function<void()> onExitScope_;
        bool dismissed_;

        // noncopyable
    private:
        ScopeGuard(ScopeGuard const&);
        ScopeGuard& operator=(ScopeGuard const&);
    };
}

inline std::unique_ptr<detail::ScopeGuard> CreateScopeGuard(std::function<void()> onExitScope)
{
    return std::unique_ptr<detail::ScopeGuard>(new detail::ScopeGuard(onExitScope));
}
Flexo
  • 87,323
  • 22
  • 191
  • 272
Weipeng
  • 1,494
  • 14
  • 11
  • 2
    I don't see how this is a simplification? It also suffers from the problem that the user needs to come up with a unique name for each instance (which is the entire purpose of the macros in the implementation provide in the question). – ronag Oct 18 '11 at 11:10
  • And what is the purpose of using std::unique_ptr here? Seems to me that you could just as well do without. – ronag Oct 18 '11 at 11:11
  • Maybe you were misunderstand pongba, the ScopeGuard is useful with the std::function(in that time, it was tr1::function). If you could read Chinese you can read the post(http://mindhacks.cn/2012/08/27/modern-cpp-practices/), or translate it with google. – jtianling Oct 23 '15 at 17:33
0

my $0.02

struct at_scope_end
{
    std::function < void () > Action;

    at_scope_end (std::function < void () > Action) :
        Action (Action)
    {
    }

    ~at_scope_end ()
    {
        Action ();
    }
};

#define AT_SCOPE_END_CAT(x,y)    x##y
#define AT_SCOPE_END_ID(index)   AT_SCOPE_END_CAT(__sg, index)
#define AT_SCOPE_END(expr)      at_scope_end AT_SCOPE_END_ID(__LINE__) ( [&] () { expr; } );
nate
  • 1,771
  • 12
  • 17