【问题标题】:The simplest and neatest c++11 ScopeGuard最简单最整洁的c++11 ScopeGuard
【发布时间】:2012-05-03 11:04:41
【问题描述】:

我正在尝试编写一个简单的ScopeGuard based on Alexandrescu concepts,但使用 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。如果 adquire lambda 抛出,则永远不会调用 release 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;
}

所以为了完整起见,我想把完整的代码放在这里,包括测试:


#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();
            }
        };
    }
}

【问题讨论】:

  • 您遗漏的一件事是这段代码不能完全编译。保护变量的声明缺少模板参数。
  • @lursher 我不是你的编译器出于同样的原因,代码仍然无法编译。如果您在发布代码之前尝试编译代码,就可以避免浪费大家的时间。
  • @lurscher,如果我是你,我不会让commited 可变,但我会从commit() 中删除const - 毕竟,commit() 改变了对象——改变很重要;为什么改变对象状态重要部分的函数会被标记为const
  • @Griwes,因为对临时的引用只能与 const 引用一起保存。这在关于 ScopeGuard 的原始 Alexandrescu 文章中进行了解释
  • @lurscher 或带有右值引用。

标签: c++ lambda c++11 exception-safety scopeguard


【解决方案1】:

更短:我不知道你们为什么坚持将模板放在警卫类中。

#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;
};

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

用法:

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

// step 2
step2();
guard1.dismiss();

我的灵感与 OP 的DrDobbs article 相同。


编辑 2017/2018:在观看了 André 链接到的(部分)Andrei's presentation 之后(我跳到最后,它说“痛苦地接近理想!”)我意识到这是可行的。大多数时候,您不想为所有事情都配备额外的警卫。你只是做一些事情,最后它要么成功,要么应该发生回滚。

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;
};

用法:

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

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

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

// ...

【讨论】:

  • “我不知道你们为什么坚持把模板放在警卫类上”——我们没想到把它隐藏在 std::function 的类型擦除后面!
  • 请注意,如果捕获的变量不适合std::function 的就地缓冲区,则会产生动态分配。
  • @mpark 如果这是一个问题,您可以使用函数指针,例如你可以写scope_guard guard1 = revert1;
  • 另外,std::function 使用间接级别,因此开销更大。
  • throw() 在 C++11 中被弃用,noexcept 取代它。
【解决方案2】:

Boost.ScopeExit 是一个需要处理非 C++11 代码的宏,即无法访问该语言中的 lambda 的代码。它使用了一些巧妙的模板技巧(比如滥用模板和比较运算符使用&lt; 产生的歧义!)和预处理器来模拟 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 ));
}

【讨论】:

  • R. Martinho,阅读 mirk 发布的 Herb Sutter 博客,对临时对象的 const 引用不是未定义的行为
  • @lurscher 不,你回去仔细阅读。那里的函数按值返回。
  • 对,对,对...所以这不会发生,因为返回类型引用已经在返回时杀死了临时对象,感谢您睁开眼睛,我不会注意到
  • @lurscher 这里有一个非常全面的解释:stackoverflow.com/a/8610728/46642。这不是您每天都会使用的功能,但是在编写通用代码时,最好尽可能通用。 :)
  • 难道你不能通过使用非模板类并将清理函数存储在 std::function 中来显着简化吗?
【解决方案3】:

您可能有兴趣看到这个由 Andrei 自己撰写的 presentation 关于如何使用 c++11 改进 scopedguard

【讨论】:

  • 如果您也在这里总结安德烈对 OP 问题的回答,我可能会投票赞成...如果该链接失效会怎样?
  • 是的,需要摘要 - 链接是一个 70 分钟长的视频演示。
  • ScopeGuard 的实现细节从视频的 1:19:30 开始。然而,演示者 Andrei 也维护了 Loki library,其中包括一个 ScopeGuard 实现。值得一看。
【解决方案4】:

您可以将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 中的删除器函数会回滚以前插入的值,如果在异常处于活动状态时离开了范围。如果您更喜欢显式提交,您可以删除删除函数中的uncaugth_exception() 问题,并在释放指针的块p.release() 末尾添加。请在此处查看Demo

【讨论】:

    【解决方案5】:

    我使用它就像一个魅力,没有额外的代码。

    shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
    

    【讨论】:

    • ...唉,它分配内存:-\
    • 我没有注意到问题中说“它不应该分配内存”的任何内容。 c++ 中很少有不是 c 的东西不分配内存。我早就放弃了尝试用 C++ 做好事情,他们把语言弄得这么乱是没有意义的。 CPU 速度如此之快,以至于我不太关心为没有添加代码的内置范围保护分配一点内存。但这就是我,你的里程可能会有所不同。
    • 性能在某些情况下可能很重要(例如在 nothrow 函数中),有些人仍然编写可以处理 std::bad_alloc 的代码(或至少尝试)。
    • 这似乎很愚蠢。 linux至少不能让内存分配失败。如果内存不足,OOM 杀手将杀死一个进程以释放内存。可能是你的。这个问题很久以前就解决了。
    • 不苦涩,紧张。 :-) 问题是现在被认为是好的软件的门槛太低了,当很多时候正面测试用例都不起作用时,很难担心内存不足的情况。
    【解决方案6】:

    通过提案P0052R0,这种方法有可能在 C++17 或 Library Fundamentals TS 中标准化

    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 有相同的警告,因为您必须存储返回值,否则析构函数将立即被调用,它不会按预期工作。

    【讨论】:

      【解决方案7】:

      大多数其他解决方案都涉及 lambda 的 move,例如通过使用 lambda 参数来初始化 std::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.
      }
      

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

      【讨论】:

      • 很抱歉回答您 4 岁的问题。但是你能解释一下 `template OnExit(F&& frv) -> OnExit;` 的实际作用吗?或者我在哪里找到那里实际发生的事情?或者给我参考资料,我可以在那里阅读?
      • @Nextar 查“演绎指南”(我在最后一句解释)
      • 我喜欢这个解决方案,但是不移动 lambda 有什么好处...?
      • @jb 如果 lambda 有捕获,那么移动它可能会很昂贵或不正确
      【解决方案8】:

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

      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;})
      

      用法

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

      【讨论】:

        【解决方案9】:

        makeScopeGuard 返回一个常量引用。您不能将此 const 引用存储在调用方的 const ref 中,如下所示:

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

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

        Herb Sutter GOTW 88 提供了一些关于在 const 引用中存储值的背景。

        【讨论】:

        • mirk,正是那篇博客文章说允许的,不是未定义的行为:“通常,临时对象只持续到它出现的完整表达式的结尾。但是,C++ 故意指定将临时对象绑定到堆栈上对 const 的引用可将临时对象的生命周期延长到引用本身的生命周期,从而避免常见的悬空引用错误。”
        • @lurscher 它出现的完整表达式在return 语句中。函数结束时,临时对象被销毁。
        • @lurscher 确实如此。但是请注意,在博客中,函数返回值。在上述问题的代码中,引用由函数返回。
        • @mirk,是的。我错过了在 Sutter 示例中函数按值返回的事实,而我的函数按引用返回,谢谢!
        • 要禁止警告unused variable 'a' [-Wunused-variable] 使用[[gnu::unused]] 属性:[[gnu::unused]] auto const &amp; a = ...
        【解决方案10】:

        FWIW 我认为 Andrei Alexandrescu 在他的 CppCon 2015 关于“声明式控制流”的演讲中使用了非常简洁的语法(videoslides)。

        以下代码深受其启发:

        Try It Online GitHub Gist

        #include <iostream>
        #include <type_traits>
        #include <utility>
        
        using std::cout;
        using std::endl;
        
        template <typename F>
        struct ScopeExitGuard
        {
        public:
            struct Init
            {
                template <typename G>
                ScopeExitGuard<typename std::remove_reference<G>::type>
                operator+(G&& onScopeExit_)
                {
                    return {false, std::forward<G>(onScopeExit_)};
                }
            };
        
        private:
            bool m_callOnScopeExit = false;
            mutable F m_onScopeExit;
        
        public:
            ScopeExitGuard() = delete;
            template <typename G> ScopeExitGuard(const ScopeExitGuard<G>&) = delete;
            template <typename G> void operator=(const ScopeExitGuard<G>&) = delete;
            template <typename G> void operator=(ScopeExitGuard<G>&&) = delete;
        
            ScopeExitGuard(const bool callOnScopeExit_, F&& onScopeExit_)
            : m_callOnScopeExit(callOnScopeExit_)
            , m_onScopeExit(std::forward<F>(onScopeExit_))
            {}
        
            template <typename G>
            ScopeExitGuard(ScopeExitGuard<G>&& other)
            : m_callOnScopeExit(true)
            , m_onScopeExit(std::move(other.m_onScopeExit))
            {
                other.m_callOnScopeExit = false;
            }
        
            ~ScopeExitGuard()
            {
                if (m_callOnScopeExit)
                {
                    m_onScopeExit();
                }
            }
        };
        
        #define ON_SCOPE_EXIT_GUARD_VAR_2(line_num) _scope_exit_guard_ ## line_num ## _
        #define ON_SCOPE_EXIT_GUARD_VAR(line_num) ON_SCOPE_EXIT_GUARD_VAR_2(line_num)
        // usage
        //     ON_SCOPE_EXIT <callable>
        //
        // example
        //     ON_SCOPE_EXIT [] { cout << "bye" << endl; };
        #define ON_SCOPE_EXIT                             \
            const auto ON_SCOPE_EXIT_GUARD_VAR(__LINE__)  \
                = ScopeExitGuard<void*>::Init{} + /* the trailing '+' is the trick to the call syntax ;) */
        
        
        int main()
        {
            ON_SCOPE_EXIT [] {
                cout << "on scope exit 1" << endl;
            };
        
            ON_SCOPE_EXIT [] {
                cout << "on scope exit 2" << endl;
            };
        
            cout << "in scope" << endl;  // "in scope"
        }
        // "on scope exit 2"
        // "on scope exit 1"
        

        对于您的用例,您可能还对std::uncaught_exception() and std::uncaught_exceptions() 感兴趣,以了解您是“正常”退出范围还是在引发异常之后:

        ON_SCOPE_EXIT [] {
            if (std::uncaught_exception()) {
                cout << "an exception has been thrown" << endl;
            }
            else {
                cout << "we're probably ok" << endl;
            }
        };
        

        HTH

        【讨论】:

        • 谢谢。正如你所看到的,这个问题比 Alexandrescu 的演讲早了 3 年,所以到那时我们已经弄清楚了 :-)
        【解决方案11】:

        这是另一个,现在是@kwarnke 的变体:

        std::vector< int > v{ };
        
        v.push_back( 42 );
        
        std::shared_ptr< void > guard( nullptr , [ & v ] ( auto )
        {
            v.pop_back( );
        } );
        

        【讨论】:

          【解决方案12】:

          这是我在 C++17 中提出的一个。将其移植到 C++11 和/或添加停用选项很简单:

          template<class F>
          struct scope_guard
          {
              F f_;
              ~scope_guard() { f_(); }
          };
          
          template<class F> scope_guard(F) -> scope_guard<F>;
          

          用法:

          void foo()
          {
              scope_guard sg1{ []{...} };
              auto sg2 = scope_guard{ []{...} };
          }
          

          编辑:在同一键中,这里的警卫只在“异常”时关闭:

          #include <exception>
          
          template<class F>
          struct xguard
          {
              F   f_;
              int count_ = std::uncaught_exceptions();
          
              ~xguard() { if (std::uncaught_exceptions() != count_) f_(); }
          };
          
          template<class F> xguard(F) -> xguard<F>;
          

          用法:

          void foobar()
          {
              xguard xg{ []{...} };
              ...
              // no need to deactivate if everything is good
          
              xguard{ []{...} },   // will go off only if foo() or bar() throw
                  foo(),
                  bar();
          
              // 2nd guard is no longer alive here
          }
          

          【讨论】:

          • 加一个给我看std::uncaught_exceptions。我不知道的很酷的设备
          • @lurscher 还注意到这个解决方案是最有效的——根本没有复制/移动,也适用于函数指针。如果只有 C++ 有两个 dtor——用于“正常离开”和“展开”的情况...... :)
          【解决方案13】:

          你已经选择了一个答案,但无论如何我都会接受挑战:

          #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.
          

          您不再拥有创建函数和构造函数,而是仅限于创建函数,主构造函数是private。我不知道如何将friend-ed 实例化限制为仅涉及当前模板参数的实例化。 (也许是因为该参数仅在返回类型中提及。)也许可以在此站点上询问对此的修复。由于第一个动作不需要存储,它只存在于创建函数中。如果第一个操作中的throwing 触发回滚,则有一个布尔参数来标记。

          std::decay 部分去除了 cv 限定符和参考标记。但如果输入类型是内置数组,则不能将其用于此通用目的,因为它也会应用数组到指针的转换。

          【讨论】:

            【解决方案14】:

            又一个答案,但恐怕我发现其他人都缺乏某种方式。值得注意的是,接受的答案可以追溯到 2012 年,但它有一个重要的错误(请参阅this comment)。由此可见测试的重要性。

            Here 是一个 >=C++11 scope_guard 的实现,它是公开可用并经过广泛测试的。它的意思是/拥有:

            • 现代、优雅、简单(主要是单功能界面,没有宏)
            • 一般(接受任何尊重前提条件的可调用对象)
            • 仔细记录
            • 瘦回调包装(未添加 std::function 或虚拟表惩罚)
            • 适当的异常规范

            另见the full list of features

            【讨论】:

            • 比Boost.ScopeExit有优势吗?
            • @MaxBarraclough 它针对 >= C++11,所以没有宏或捕获技巧(留给 lambdas);它可以防止被遗忘的退货;可以选择在 C++17 中强制执行 noexcept;具有现代异常规范(noexcept);对 sfinae 友好;单个“拖放”标题;未经许可。
            • 谢谢。防止被遗忘的退货是什么意思?无论如何,那是UB,不是吗?
            猜你喜欢
            • 2014-07-05
            • 2019-12-02
            • 1970-01-01
            • 2012-03-13
            • 1970-01-01
            • 2012-09-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多