C ++ 11范围退出后卫,一个好主意?

时间:2010-09-08 16:24:41

标签: c++ c++11

我为C ++ 11编写了一个小实用程序类,我将其用作范围防护,以便更轻松地处理异常安全和类似的事情。

看起来有点像黑客。但我很惊讶我没有在使用C ++ 11功能的其他地方看到它。我认为对于C ++ 98来说,boost类似。

但这是个好主意吗?或者是否有我错过的潜在问题?在boost或类似的解决方案中是否已有类似的解决方案(使用C ++ 11功能)?

    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_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)

它使用了类似的东西。

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

10 个答案:

答案 0 :(得分:19)

  

但这是个好主意吗?

不确定。相关主题是RAII paradigm

  

或者在那里   我错过了潜在的问题?

您不处理例外。

  

时   已经有类似的解决方案了   C ++ 0x功能)在boost还是类似的?

Alexandrescu很久以前就提出ScopeGuard了。 Boost和std::tr1都有一个名为scoped_ptrshared_ptr的东西(带有自定义删除工具),可以让您完成此任务。

答案 1 :(得分:17)

记录中有Boost ScopeExit

答案 2 :(得分:9)

范围守卫绝对是个好主意。我认为范围保护概念是异常安全的有力工具。如果您可以使用C ++ 0x语法制作更安全,更清晰的Boost ScopeExit版本,我认为这非常值得您花时间。

类似于Alexandrescu的ScopeGuard和Boost的ScopeExit,D programming language有直接的语法来处理这类事情。 D编程团队认为范围保护是一个很好的想法,他们添加了它directly to the language(即它没有在库中实现)。

实施例

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

基于范围的警卫不是什么新事物。它的功能可以通过类析构函数(RAII和所有这些)轻松复制。也可以用C#或Java中的try/finally替换。哎呀,即使是pthreads提供了一个基本的守卫,称为pthread_cleanup_push

当你在函数中有多个scope(*)语句时,范围保护如此强大的原因。它的扩展性非常好,而try/finally则需要超级人力来管理两个以上的任何东西。

答案 3 :(得分:5)

如果用二元运算符替换create_scope_exit,我们可以删除括号:

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

用法:

auto atScopeExit = at_scope_exit() == [&]
{
    ...
};

<强> UPD:
相应的宏:

#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__]

答案 4 :(得分:1)

为了记录,TS 3中有scope_exit

答案 5 :(得分:0)

使用tr1::functiontr1::unique_ptr可以大大简化实施,如下所示:

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

答案 6 :(得分:0)

我们可以通过将它放在define:

中来省略丑陋的[&amp;]内容
#define UTILITY_SCOPE_EXIT(f) const auto& _UTILITY_EXIT_SCOPE_LINENAME(EXIT, __LINE__) = ::detail::create_scope_exit([&]f)

然后:

UTILITY_SCOPE_EXIT({myfile.close();});

使用MSVC ++ 11.0(VS2012)测试。问候。

答案 7 :(得分:0)

这是一个好主意,但是你上课时会遇到一些问题。

  1. 你应该禁用新的操作符(你不想让用户以强制调用删除的方式使用它,对吗?)
  2. 您需要一个“提交”功能,以使其成为scope guard而不是简单的RAII
  3. 请注意,如果实施第2点,则需要为实例化的每个范围保护提供有意义的名称。一般来说,这不是问题,但它可能在您的应用程序中(或根据您的口味)。

    最后,这个问题可能更适合CodeReview

答案 8 :(得分:0)

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

用法:

if (gdiplus::GdiplusStartup(&token, &startupInput, nullptr) == Gdiplus::Ok) {
    defer({
        gdiplus::GdiplusShutdown(token);
    });
    ...
}

答案 9 :(得分:-1)

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