最简单最新的c ++ 11 ScopeGuard

我试图写一个基于Alexandrescu概念的简单的ScopeGuard,但用c ++ 11的习惯用法。

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<typename rLambda> const ScopeGuard< rLambda >& makeScopeGuard(const rLambda& _r) { return ScopeGuard< rLambda >(_r ); } } 

这是用法:

 void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptions() { std::vector<int> myVec; std::vector<int> 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(); } 

由于我的版本比大多数例子都短(比如Boost ScopeExit),所以我想知道我要抛弃什么特性。 希望我在这里80/20的情况下(我有80%的整齐,20%的代码行),但我不禁想知道如果我失去了一些重要的东西,或有一些缺点值得提到这个版本的ScopeGuard成语

谢谢!

编辑我注意到了makeScopeGuard的一个非常重要的问题,它在构造函数中使用了adquire lambda。 如果需求lambda抛出,那么释放lambda永远不会被调用,因为范围守护从未完全构造。 在许多情况下,这是所期望的行为,但是我觉得有时候还需要一个在发生抛出时会调用回滚的版本:

 //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<aLambda>(_a) , std::forward<rLambda>(_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<rLambda>(_r )); // *** no longer UB, because we're returning by value _a(); return scope; } 

所以为了完整性,我想在这里把完整的代码,包括testing:


 #include <vector> 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<AdquireLambda>(_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>(_l)) { std::forward<AdquireLambda>(_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<aLambda>(_a) , std::forward<rLambda>(_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<rLambda>(_r )); // *** no longer UB, because we're returning by value _a(); return scope; } template<typename rLambda> ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r) { return ScopeGuard< rLambda >( std::forward<rLambda>(_r )); } namespace basic_usage { struct Test { std::vector<int> myVec; std::vector<int> 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(); } }; } } 

Boost.ScopeExit是一个需要与非C ++ 11代码一起工作的macros,即无法访问语言中的lambdaexpression式的代码。 它使用了一些巧妙的模板hack(比如滥用模板和比较运算符的模糊性)和预处理器来模拟lambda特性。 这就是代码更长的原因。

显示的代码也是错误的(这可能是使用现有解决scheme的最强的原因):由于返回对临时对象的引用,它会调用未定义的行为。

由于您正在尝试使用C ++ 11特性,因此可以通过使用移动语义,右值引用和完美转发来改进代码:

 template< typename Lambda > class ScopeGuard { bool committed; // not mutable Lambda rollbackLambda; public: // make sure this is not a copy ctor template <typename L, DisableIf<std::is_same<RemoveReference<RemoveCv<L>>, ScopeGuard<Lambda>>> =_ > /* 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>(_l)) // avoid copying unless necessary {} template< typename AdquireLambda, typename L > ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l)) { std::forward<AdquireLambda>(_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<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value } template<typename rLambda> ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r) { return ScopeGuard< rLambda >( std::forward<rLambda>(_r )); } 

甚至更短:我不知道你们为什么坚持把模板放在守卫class上。

 #include <functional> class scope_guard { public: template<class Callable> scope_guard(Callable && undo_func) : f(std::forward<Callable>(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() noexcept { f = nullptr; } scope_guard(const scope_guard&) = delete; void operator = (const scope_guard&) = delete; private: std::function<void()> f; }; 

请注意,清理代码不会抛出是非常重要的,否则就会出现与抛出析构函数类似的情况。

用法:

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

我的灵感和DrDobbs的文章一样。


编辑2017年:在观看( 安德烈的 André链接到的(一些) 的介绍 (好吧,我跳到了它说:“痛苦接近理想!”的结尾),我意识到这几乎是可行的。 除了小的.dismiss()调用。 但是,大多数情况下,你不希望有一个额外的卫兵的一切。 你只是做一些东西,最后它要么成功,要么发生回滚。 所以:

 #include <functional> #include <deque> class scope_guards : public std::deque<std::function<void()>> { public: template<class Callable> scope_guards& operator += (Callable && undo_func) { emplace_front(std::forward<Callable>(undo_func)); } ~scope_guards() { for(auto &f : *this) f(); // must not throw } void dismiss() noexcept { clear(); } scope_guards() = default; scope_guards(const scope_guards&) = delete; void operator = (const scope_guards&) = delete; }; 

用法:

 scope_guards scope_exit, scope_fail; action1(); scope_exit += [](){ cleanup1(); }; scope_fail += [](){ rollback1(); }; action2(); scope_exit += [](){ cleanup2(); }; scope_fail += [](){ rollback2(); }; //... scope_fail.dismiss(); 

你可能有兴趣看到Andrei本人自己介绍如何用c ++ 11改进scopedguard

这种方法有可能在C ++ 17或图书馆基础TS中通过提案P0052R0进行标准化

 template <typename EF> scope_exit<see below> make_scope_exit(EF &&exit_function) noexcept; template <typename EF> scope_exit<see below> make_scope_fail(EF && exit_function) noexcept; template <typename EF> scope_exit<see below> make_scope_success(EF && exit_function) noexcept; 

乍一看,这与std::async有相同的警告,因为你必须存储返回值,否则析构函数将被立即调用,并且不会按预期工作。

你可以使用std::unique_ptr来实现RAII模式。 例如:

 vector<int> v{}; v.push_back(42); unique_ptr<decltype(v), function<void(decltype(v)*)>> p{&v, [] (decltype(v)* v) { if (uncaught_exception()) { v->pop_back(); }}}; throw exception(); // rollback p.release(); // explicit commit 

unique_ptr p的deleter函数将原先插入的值unique_ptr p ,如果范围在启用exception时保留。 如果您更喜欢显式提交,则可以删除uncaugth_exception()函数中的uncaugth_exception()问题,并在释放指针的块p.release()的末尾添加。 在这里看到演示 。

makeScopeGuard返回一个const引用。 你不能将这个const引用存储在调用方的const ref中,如:

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

所以你正在调用未定义的行为。

Herb Sutter GOTW 88给出了关于在const引用中存储值的一些背景知识。

没有承诺追踪,但非常整齐,快速。

 template <typename F> struct ScopeExit { ScopeExit(F&& f) : m_f(std::forward<F>(f)) {} ~ScopeExit() { m_f(); } F m_f; }; template <typename F> ScopeExit<F> makeScopeExit(F&& f) { return ScopeExit<F>(std::forward<F>(f)); }; #define STRING_JOIN(arg1, arg2) STRING_JOIN2(arg1, arg2) #define STRING_JOIN2(arg1, arg2) arg1 ## arg2 #define ON_SCOPE_EXIT(code) auto STRING_JOIN(scopeExit, __LINE__) = makeScopeExit([&](){code;}) 

用法

 void fn() { puts("a"); auto _ = makeScopeExit([]() { puts("b"); }); // More readable with a macro ON_SCOPE_EXIT(puts("c")); } # prints a, c, b 

你已经select了一个答案,但我会接受挑战:

 #include <iostream> #include <type_traits> #include <utility> template < typename RollbackLambda > class ScopeGuard; template < typename RollbackLambda > auto make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard<typename std::decay<RollbackLambda>::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<typename std::decay<AnyRollbackLambda>::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<typename std::decay<RollbackLambda>::type> { using std::forward; return ScopeGuard<typename std::decay<RollbackLambda>::type>{ forward<RollbackLambda>(r) }; } template < typename ActionLambda, typename RollbackLambda > auto make_ScopeGuard( ActionLambda && a, RollbackLambda &&r, bool roll_back_if_action_throws ) -> ScopeGuard<typename std::decay<RollbackLambda>::type> { using std::forward; if ( not roll_back_if_action_throws ) forward<ActionLambda>(a)(); auto result = make_ScopeGuard( forward<RollbackLambda>(r) ); if ( roll_back_if_action_throws ) forward<ActionLambda>(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. 

而不是具有创build函数和构造函数,而仅限于创build函数,主构造函数是private 。 我无法弄清楚如何将friend -ed实例化仅限于涉及当前模板参数的实例。 (也许是因为这个参数只是在返回types中提到的。)也许在这个网站上可以解决这个问题。 由于第一个动作不需要存储,它只存在于创build函数中。 有一个布尔参数标志如果从第一个动作throw触发回滚或不。

std::decay部分去除了cv-qualifiers和引用标记。 但是,如果inputtypes是内置数组,则不能将其用于此通用目的,因为它也将应用数组到指针的转换。

下面是另外一个,现在是@ ​​kwarnke的变体:

 std::vector< int > v{ }; v.push_back( 42 ); auto guard_handler = [ & v ] ( nullptr_t ptr ) { v.pop_back( ); }; std::shared_ptr< decltype( guard_handler ) > guard( nullptr , std::move( guard_handler ) );