C++11 scope exit guard, a good idea?
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_开发者_Go百科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;
}
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.
For the record, there is Boost ScopeExit.
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.
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__]
For the record, there is scope_exit
in the TS 3
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(); };
}
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));
}
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; } );
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.
This is a good idea, but there are a couple of problems with you class.
- 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?)
- 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.
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);
});
...
}
精彩评论