最简单、最整洁的C++11 ScopeGuard

49

我正在尝试编写一个基于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的情况下(即用20%的代码实现了80%的简洁),但我不禁想知道我是否错过了重要的东西,或者这个ScopeGuard习惯用法的某些缺陷值得一提。
谢谢!
编辑:我注意到一个非常重要的问题,即在构造函数中使用adquire lambda的makeScopeGuard。如果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();
            }
        };
    }
}

3
你漏掉了一件事,那就是这段代码不能完整编译。它缺少保护变量的模板参数声明。 - R. Martinho Fernandes
8
@lursher 我不是你的编译器。代码仍然因为同样的原因无法编译。如果你在发布之前尝试编译你的代码,就可以避免浪费大家的时间。 - R. Martinho Fernandes
1
@lurscher,如果我是你,我不会使committed可变的,但我会从commit()中移除const——毕竟,commit()改变对象的状态——而这个改变是重要的。为什么一个改变对象重要部分状态的函数会被标记为const - Griwes
1
@Griwes,因为只有使用const引用才能保留对临时变量的引用。这在Alexandrescu原始关于ScopeGuard的文章中有解释。 - lurscher
1
@lurscher 或者使用右值引用。 - R. Martinho Fernandes
显示剩余11条评论
14个回答

37

更简短地说,我不知道为什么你们坚持将模板放在守卫类上面。

#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文章。


2017/2018编辑:在观看了André链接的Andrei的演讲(我跳到了最后一部分,它说“非常接近理想!”)之后,我意识到这是可行的。大多数情况下,您不需要为每件事都添加额外的保护措施。你只需做你要做的事情,最后要么成功,要么回滚。

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

// ...

4
“我不知道为什么你们坚持将模板放在守卫类上面” - 我们没有考虑使用std::function的类型擦除来隐藏它! - M.M
12
请注意,如果捕获的变量不适合于std::function的就地缓冲区,则会产生动态分配。 - mpark
1
如果这是一个问题,您可以使用函数指针,例如,您可以编写scope_guard guard1 = revert1; - Fozi
3
另外,std::function<Fn>使用了一定的间接性,因此具有更多的开销。 - Brandon Kohn
1
此外,我认为 scope_guard 的构造函数和 scope_guards 的 operator+= 可能会抛出异常,这将阻止您注册需要运行的清理函数。我不确定其他任何实现是否可以解决这个问题。 - tgnottingham
显示剩余23条评论

23
Boost.ScopeExit是一个宏,需要与非C++11代码一起使用,即不能在语言中使用lambda。它使用一些巧妙的模板技巧(例如滥用使用<作为模板和比较运算符时出现的歧义!)以及预处理器来模拟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 https://dev59.com/tGkw5IYBdhLWcg3wDWEk 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 ));
}

11
不,你需要仔细阅读。那些函数是按值返回的。 - R. Martinho Fernandes
1
正确,确实如此... 所以这不会发生,因为返回类型引用已经在返回时销毁了临时变量,感谢你让我看到了这一点,我原本没有注意到。 - lurscher
1
@lurscher 这里有一个非常全面的解释:https://dev59.com/eWoy5IYBdhLWcg3wWss_#8610728。虽然这不是你每天都会用到的功能,但在编写通用代码时,尽可能地通用是很好的选择。 :) - R. Martinho Fernandes
1
你能不能使用非模板类并将清理函数存储在std::function中,从而显着简化这个问题? - Shachar Shemesh
也许这在这里不是很重要,但是Visual Studio 2015 Update 3中的lambda表达式与捕获参数相关的错误率非常低。有时,在lambda使用时发现未初始化的参数类型,从而导致访问冲突。因为Boost.ScopeExit采用相同的方法(通过[]块捕获参数),所以它也有相同的错误。解决方法是通过显式lambda参数替换捕获。 - Andry
显示剩余7条评论

18

我使用这个方法非常顺利,不需要额外的代码。

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

1
哎呀,它分配内存了 :-\ - C.M.
1
我没有注意到问题中有任何内容说“它不应分配内存”。在C++中,很少有不分配内存的东西。我早就放弃了在C++中做好事情的尝试,因为他们把这门语言搞得一团糟,所以没必要。CPU速度非常快,对于一个没有添加代码的内置作用域保护器多分配一点内存我并不介意。不过这只代表我的个人看法,你的想法可能会有所不同。 - stu
这似乎很愚蠢。Linux 至少不会失败于内存分配。如果你的内存用尽了,OOM killer 将会杀死一个进程以释放内存。可能是你的进程。这个问题早就解决了。 - stu
1
兄弟,你太苦了——放松点,事情没有你想的那么糟糕 :) 就程序控制而言,C++和C一样好。Linux可能会失败内存分配(例如在x64环境中运行x32进程时,当进程被rlimit限制或内核配置为不超额承诺时)。OOM killer不是忽略OOM的好理由。 - C.M.
1
不要苦恼,放松点。 :-) 问题在于现在被认为是好软件的标准太低了,很难担心内存溢出的情况,因为很多时候正向测试用例甚至都无法正常工作。 - stu
显示剩余13条评论

15

您可以使用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()释放指针。请参见此处示例。


15
你可能对查看这个演示文稿感兴趣,它由Andrei亲自展示了他如何运用C++11改进scopedguard的方法。

14
如果您可以总结一下安德烈在回答中对于问题的回答,我可能会投票支持您的回答。如果那个链接失效了会发生什么? - U007D
5
需要摘要 - 这个链接是一个70分钟的视频演示。 - Zitrax
ScopeGuard的实现细节从视频的1:19:30开始。然而,演讲者Andrei还维护Loki库,其中包括ScopeGuard的实现。值得一看。 - cemdervis

11

大多数其他解决方案涉及移动 lambda。例如,使用 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推断为左值引用。


很抱歉回答你四年前的问题。但是,你能解释一下template<typename F> OnExit(F&& frv) -> OnExit<F>;实际上是做什么的吗?或者告诉我在哪里可以找到实际发生的事情?或者给我一些参考资料,让我可以查阅它? - Nextar
2
@Nextar请查阅“扣除指南”(我在最后一句中解释了这个)。 - M.M
我喜欢这个解决方案,但不移动lambda的优点是什么? - j b
如果 lambda 包含了捕获变量,那么移动它可能会很昂贵或不正确。 - M.M

8

这种方法有可能通过提案P0052R0在C++17或者库基础 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 有相同的注意事项,因为你必须存储返回值,否则析构函数将会立即被调用,并且它不会按照预期工作。


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

用法

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

4

makeScopeGuard返回一个const引用,你不能在调用方的代码行中将这个const引用存储在一个const引用中:

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

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

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


Mirk,确切地说,该博客文章指出这是允许的,不是未定义行为:“通常,临时对象仅持续到它所在的完整表达式的结束。但是,C++有意规定将栈上的临时对象绑定到const引用会延长临时对象的生命周期,使其与引用本身的生命周期相同,从而避免了常见的悬空引用错误。” - lurscher
2
@lurscher 它所出现的完整表达式在return语句中。当函数结束时,临时变量将被销毁。 - R. Martinho Fernandes
3
@lurscher 的确。但请注意,在博客中函数返回值,而在上述问题的代码中,函数返回引用。 - mirk
@mirk,是的。我错过了Sutter示例中函数返回值的事实,而我的函数返回的是引用,谢谢! - lurscher
为了抑制警告“未使用的变量'a'[-Wunused-variable]”,请使用“[[gnu::unused]]”属性:[[gnu::unused]] auto const & a = ... - Tomilov Anatoliy

3

我认为Andrei Alexandrescu在他的CppCon 2015的演讲“声明性控制流”中使用了非常巧妙的语法(视频幻灯片)。

以下代码受其启发:

在线尝试 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()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


1
谢谢。正如您所看到的,这个问题比Alexandrescu的演讲早了3年,所以我们那时已经想明白了 :-) - lurscher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接