最简单,最新的c ++ 11 ScopeGuard

lur*_*her 31 c++ lambda scopeguard exception-safety c++11

我正在尝试编写一个基于Alexandrescu概念但使用c ++ 11习语的简单ScopeGuard.

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 );
    }
}
Run Code Online (Sandbox Code Playgroud)

这是用法:

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();
}
Run Code Online (Sandbox Code Playgroud)

由于我的版本比大多数示例(如Boost ScopeExit)短,我想知道我要遗漏的特色.希望我在这里的80/20场景(我有80%的整洁度,20%的代码行),但我不禁想知道我是否遗漏了一些重要的东西,或者是否有一些缺点值得提到这个版本的ScopeGuard成语

谢谢!

编辑我注意到makeScopeGuard的一个非常重要的问题,它在构造函数中获取了adquire lambda.如果adquire 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;
}
Run Code Online (Sandbox Code Playgroud)

所以为了完整性,我想在这里填写完整的代码,包括测试:


#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();
            }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

Foz*_*ozi 30

更短:我不知道为什么你们坚持把模板放在后卫班上.

#include <functional>

class scope_guard {
public: 
    template<class Callable> 
    scope_guard(Callable && undo_func) try : f(std::forward<Callable>(undo_func)) {
    } catch(...) {
        undo_func();
        throw;
    }

    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;
};
Run Code Online (Sandbox Code Playgroud)

请注意,清理代码不会抛出是必要的,否则您会遇到与抛出析构函数类似的情况.

用法:

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

// step 2
step2();
guard1.dismiss();
Run Code Online (Sandbox Code Playgroud)

我的灵感与DrDobbs的文章相同.


编辑2017/2018:在看了安德烈与安德烈联系的(一些)演讲之后(我跳到最后说"痛苦接近理想!")我意识到这是可行的.大多数时候你不想为所有事情提供额外的警卫.你只需做一些事情,最后它要么成功要么应该回滚.

编辑2018:添加了执行策略,删除了dismiss呼叫的必要性.

#include <functional>
#include <deque>

class scope_guard {
public:
    enum execution { always, no_exception, exception };

    scope_guard(scope_guard &&) = default;
    explicit scope_guard(execution policy = always) : policy(policy) {}

    template<class Callable>
    scope_guard(Callable && func, execution policy = always) : policy(policy) {
        this->operator += <Callable>(std::forward<Callable>(func));
    }

    template<class Callable>
    scope_guard& operator += (Callable && func) try {
        handlers.emplace_front(std::forward<Callable>(func));
        return *this;
    } catch(...) {
        if(policy != no_exception) func();
        throw;
    }

    ~scope_guard() {
        if(policy == always || (std::uncaught_exception() == (policy == exception))) {
            for(auto &f : handlers) try {
                f(); // must not throw
            } catch(...) { /* std::terminate(); ? */ }
        }
    }

    void dismiss() noexcept {
        handlers.clear();
    }

private:
    scope_guard(const scope_guard&) = delete;
    void operator = (const scope_guard&) = delete;

    std::deque<std::function<void()>> handlers;
    execution policy = always;
};
Run Code Online (Sandbox Code Playgroud)

用法:

scope_guard scope_exit, scope_fail(scope_guard::execution::exception);

action1();
scope_exit += [](){ cleanup1(); };
scope_fail += [](){ rollback1(); };

action2();
scope_exit += [](){ cleanup2(); };
scope_fail += [](){ rollback2(); };

// ...
Run Code Online (Sandbox Code Playgroud)

  • 请注意,如果捕获的变量不适合`std :: function`的就地缓冲区,则会产生动态分配. (11认同)
  • "我不知道为什么你们坚持把模板放在防护等级上" - 我们没想到将它隐藏在std :: function的类型擦除之后! (4认同)
  • 此外, std::function&lt;Fn&gt; 使用了一个间接级别,因此有更多的开销。 (2认同)

R. *_*des 22

Boost.ScopeExit是一个需要使用非C++ 11代码的宏,即无法访问该语言中的lambdas的代码.它使用了一些聪明的模板黑客(比如滥用<模板和比较运算符使用产生的歧义!)和预处理器来模拟lambda特性.这就是代码更长的原因.

显示的代码也是错误的(这可能是使用现有解决方案的最强大的原因):由于返回对临时数据库的引用而调用未定义的行为.

由于您正在尝试使用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 ));
}
Run Code Online (Sandbox Code Playgroud)

  • @lurscher不,你回去仔细阅读.那里的函数按值返回. (11认同)

小智 15

您可能有兴趣看到Andrei自己在演讲中如何使用c ++ 11改进scopedguard

  • 如果你还要在这里总结一下安德烈对OP问题的回答,我可能会投票给答案......如果这个链接腐烂怎么办? (12认同)
  • 是需要摘要 - 该链接是一个70分钟的视频演示. (4认同)

kwa*_*nke 12

您可以将其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
Run Code Online (Sandbox Code Playgroud)

unique_ptr p如果在异常处于活动状态时保留范围,则删除函数将滚动先前插入的值.如果您更喜欢显式提交,则可以删除uncaugth_exception()删除函数中的问题,并在p.release()释放指针的块的末尾添加.在这里看演示.


stu*_*stu 10

我使用它的过程就像一种魅力,不需要任何额外的代码。

shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
Run Code Online (Sandbox Code Playgroud)


Eri*_*zen 7

这种方法有可能在C++ 17或Library Fundamentals 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;
Run Code Online (Sandbox Code Playgroud)

乍一看,这有一个警告,std::async因为您必须存储返回值,否则将立即调用析构函数,它将无法按预期工作.


M.M*_*M.M 7

大多数其他解决方案涉及movelambda 的 a,例如,通过使用 lambda 参数来初始化 astd::function或从 lambda 推导出的类型的对象。

这是一个非常简单的方法,允许使用命名的 lambda 而不移动它(需要 C++17):

template<typename F>
struct OnExit
{
    F func;
    OnExit(F&& f): func(std::forward<F>(f)) {}
    ~OnExit() { func();  }
};

template<typename F> OnExit(F&& frv) -> OnExit<F>;

int main()
{
    auto func = []{ };
    OnExit x(func);       // No move, F& refers to func
    OnExit y([]{});       // Lambda is moved to F.
}
Run Code Online (Sandbox Code Playgroud)

当参数是左值时,推导指南使 F 推导为左值引用。

  • @Nextar查找“推导指南”(我在最后一句解释了这一点) (2认同)

ens*_*ens 5

没有承诺跟踪,但非常整洁和快速。

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;})
Run Code Online (Sandbox Code Playgroud)

用法

{
    puts("a");
    auto _ = makeScopeExit([]() { puts("b"); });
    // More readable with a macro
    ON_SCOPE_EXIT(puts("c"));
} # prints a, c, b
Run Code Online (Sandbox Code Playgroud)