【问题标题】:C++11 scope exit guard, a good idea?C ++ 11范围出口保护,好主意吗?
【发布时间】:2010-09-08 16:24:40
【问题描述】:

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

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

但这是个好主意吗?还是有我遗漏的潜在问题?在 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;
}

【问题讨论】:

  • 您可能想看看我的惰性 RAII 课程:stackoverflow.com/questions/2419650/…。请注意,您的 scope_exit 依赖于复制构造函数消除。如果你在没有优化的情况下编译这个 sn-p,你调用 scope-exit lambda 两次。请参阅我的 RAII 课程,了解如何解决该问题。
  • 我认为 myfile 超出范围时将被关闭。它应该已经是异常安全的了。
  • 这是一个简单的 C++11 范围保护实现,它不依赖于复制构造函数的消除,也不会冒着悬空指向临时对象的风险:stackoverflow.com/a/12545195/558823
  • 另一个潜在问题是您为宏使用保留名称...

标签: c++ c++11


【解决方案1】:

但这是个好主意吗?

当然。一个相关主题是RAII paradigm

或者有吗 我错过的潜在问题?

你不处理异常。

是 已经有类似的解决方案(与 C++0x 特性)在 boost 中还是类似的?

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

【讨论】:

【解决方案2】:

为了记录,有Boost ScopeExit

【讨论】:

    【解决方案3】:

    范围保护绝对是个好主意。我认为范围保护概念是异常安全的有力工具。如果您可以使用 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 则需要超能力来管理两个以上的东西。

    【讨论】:

      【解决方案4】:

      如果用二元运算符替换 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() == [&]
      {
          ...
      };
      

      更新:
      对应宏:

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

      【讨论】:

      • 这个版本的问题是你必须为每个“at_scope_exit”取一个唯一的名字。
      • @ronag 你的意思是“对于每个 atScopeExit”吗?您可以在代码中使用带有 LINE 的宏。
      【解决方案5】:

      为了记录,TS 3中有scope_exit

      【讨论】:

        【解决方案6】:

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

        【讨论】:

        • 我看不出这是如何简化的?它还面临用户需要为每个实例提供唯一名称的问题(这是问题中提供的实现中宏的全部目的)。
        • 这里使用 std::unique_ptr 的目的是什么?在我看来,你也可以不用。
        • 也许你误解了乒乓球,ScopeGuard 对 std::function 很有用(当时是 tr1::function)。如果你会看中文你可以看帖子(mindhacks.cn/2012/08/27/modern-cpp-practices),或者用谷歌翻译。
        【解决方案7】:

        我们可以通过将丑陋的 [&] 放在定义中来省略它:

        #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) 测试。问候。

        【讨论】:

          【解决方案8】:

          这是个好主意,但是你的课有几个问题。

          1. 您应该禁用 new 运算符(您不希望用户以强制调用 delete 的方式使用它,对吧?)
          2. 您需要一个“提交”函数,以使其成为 scope guard 而不是简单的 RAII

          请注意,如果您实施第 2 点,则您需要为您实例化的每个范围守卫指定一个有意义的名称。一般来说,这不是问题,但它可能存在于您的应用程序中(或符合您的口味)。

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

          【讨论】:

            【解决方案9】:

            使用升压:

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

            【讨论】:

              【解决方案10】:

              我使用了一个非常轻量级的宏解决方案:

              #define defer(code) std::unique_ptr<void, void (*)(void*)> _defer##__LINE__((void*)1, [&](void*) { code });
              

              像这样使用它:

              {
                  defer({ last_code_to_execute_on_scope_exit(); })
                  ...
                  defer({ first_code_to_execute_on_scope_exit(); })
              }
              

              【讨论】:

                【解决方案11】:

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2011-01-06
                  • 2010-09-17
                  • 2015-02-03
                  • 2021-12-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2013-06-03
                  相关资源
                  最近更新 更多