zoukankan      html  css  js  c++  java
  • 闲谈scope gurad机制

      RAII(资源获取即初始化)机制给我们的编程带来了极大的帮助,C++标准库中的std::lock_guard就使用到了该机制。它的作用是:guard对象在构造的时候将资源初始化好,在guard对象析构的时候将资源自动释放掉;完美的解决了过去C++程序员需要手动或异常情况下管理资源释放的问题。那么"socpe guard"机制是什么呢?

    一、scope guard用来解决什么问题

      如下代码:

     1 void XXX::InsertUser(const User& user)
     2 {
     3     _userVec.push_back(user);
     4     try
     5     {
     6         _db.Insert("INSERT USE_TABLE "); // 该方法可能会抛异常.
     7     }catch (const std::exception&)
     8     {
     9 
    10     }
    11 }

      该方法主要是将用户数据插入到数据库并写入缓存,插入数据库的方法可能抛出异常,导致该条数据无法入库,但是缓存中依然保存这条数据。我们能否在异常或其他情况下将数据动态回滚呢?答案是可以,scope guard机制可以帮助我们达到这种效果。那么什么是scope guard机制呢?

      概括来说:首先我们需要设计一个scope_guard对象,在构造的时候将回滚方法作为形参传递给scope guard对象;scope guard析构的时候,我们根据条件决定是否回滚。它是基于RAII机制实现的。不同于RAII机制,析构是否调用回滚函数根据用户的设置决定。

    二、scope_guard类模板实现

      摘录自:git@github.com:ricab/scope_guard.git,对其中的几个重点部分做了注释说明。

      1          // 重点是理解这里:编译器是如何通过类型萃取得到第二个参数的.
      2          // std::enable_if<is_proper_sg_callback_t<Callback>::value > ::type >
      3          // 判断Callback是不是不带形参和返回为void的回调函数
      4          // 比如:void Rollback 或 lambda = [](){};
      5             template<typename Callback,
      6             typename = typename std::enable_if<
      7             is_proper_sg_callback_t<Callback>::value>::type>
      8             class scope_guard;
      9 
     10         // 声明友元函数
     11         template<typename Callback>
     12         detail::scope_guard<Callback> make_scope_guard(Callback&& callback)
     13             noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); 
     14 
     15        template<typename Callback>
     16         class scope_guard<Callback> final
     17         {
     18         public:
     19             typedef Callback callback_type;
     20 
     21             scope_guard(scope_guard&& other)
     22                 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value);
     23 
     24             ~scope_guard() noexcept; // highlight noexcept dtor
     25 
     26             void dismiss() noexcept;
     27 
     28         public:
     29             scope_guard() = delete;
     30             scope_guard(const scope_guard&) = delete;
     31             scope_guard& operator=(const scope_guard&) = delete;
     32             scope_guard& operator=(scope_guard&&) = delete;
     33 
     34         private:
     35             explicit scope_guard(Callback&& callback)
     36                 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
     37                                                                 meant for friends only */
     38             // 友元函数: 方便访问scope_guard对象的非公有方法或属性.
     39             friend scope_guard<Callback> make_scope_guard<Callback>(Callback&&)
     40                 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value); /*
     41                 only make_scope_guard can create scope_guards from scratch (i.e. non-move)
     42                 */
     43 
     44         private:
     45             Callback m_callback;
     46             bool m_active;
     47 
     48         };
     49 
     50 ///////////////////////////////////////////////////////////////////////////////
     51 template<typename Callback>
     52 sg::detail::scope_guard<Callback>::scope_guard(Callback&& callback)
     53 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
     54     : m_callback(std::forward<Callback>(callback)) /* use () instead of {} because
     55       of DR 1467 (https://is.gd/WHmWuo), which still impacts older compilers
     56       (e.g. GCC 4.x and clang <=3.6, see https://godbolt.org/g/TE9tPJ and
     57       https://is.gd/Tsmh8G) */
     58     , m_active{ true }
     59 {}
     60 
     61 ////////////////////////////////////////////////////////////////////////////////
     62 template<typename Callback>
     63 // 源码是如下注释的部分, visual studio 2019版本会提示 但是编译都没问题
     64 // /sg::detail::scope_guard<Callback>::~scope_guard<Callback>() noexcept
     65 // 但是这里去掉了<Callback>, 该问题已经在咨询源码作者为啥不采用
     66 // sg::detail::scope_guard<Callback>::~scope_guard() noexcept
     67 // 作者已经进行了修改
     68 sg::detail::scope_guard<Callback>::~scope_guard() noexcept
     69 {
     70     if (m_active)
     71         m_callback();
     72 }
     73 
     74 ////////////////////////////////////////////////////////////////////////////////
     75 template<typename Callback>
     76 sg::detail::scope_guard<Callback>::scope_guard(scope_guard&& other)
     77 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
     78     : m_callback(std::forward<Callback>(other.m_callback)) // idem
     79     , m_active{ std::move(other.m_active) }
     80 {
     81     other.m_active = false;
     82 }
     83 
     84 ////////////////////////////////////////////////////////////////////////////////
     85 template<typename Callback>
     86 // 核心方法:dismiss,能够动态取消Callback操作.
     87 inline void sg::detail::scope_guard<Callback>::dismiss() noexcept
     88 {
     89     m_active = false;
     90 }
     91 
     92 ////////////////////////////////////////////////////////////////////////////////
     93 template<typename Callback>
     94 // 通过这个全局函数构造scope guard对象.
     95 // Folly库中也有scope guard的实现,实现比这个复杂点.
     96 inline auto sg::detail::make_scope_guard(Callback&& callback)
     97 noexcept(std::is_nothrow_constructible<Callback, Callback&&>::value)
     98 -> detail::scope_guard<Callback>
     99 {
    100     return detail::scope_guard<Callback>{std::forward<Callback>(callback)};
    101 }

      如果对上面代码推导过程不清楚的同学,可以参考上篇博客介绍的工具查看编译器是如何推导的。

      使用方式如下:

     1 static void Rollback()
     2 {
     3     std::cout << "Rollback
    ";
     4 }
     5 
     6 struct Functor
     7 {
     8     void operator()()
     9     {
    10         std::cout << "Functor
    ";
    11     }
    12 };
    13 
    14 class Test 
    15 {
    16 public:
    17     void Func()
    18     {
    19         std::cout << "Test::Func
    ";
    20     }
    21     void FuncWithParamters(int a)
    22     {
    23         std::cout << "Test::FuncWithParamters(" << a << ")
    ";
    24     }
    25 };
    26 
    27 
    28 int main()
    29 {
    30     using sg::detail::make_scope_guard;
    31     // 1 普通函数
    32     auto scopeGuard1 = make_scope_guard(Rollback);
    33     // 2 Lambda表达式
    34     auto scopeGuard2 = make_scope_guard([]() {
    35         std::cout << "Lambda expression.
    ";
    36         });
    37     // 3 仿函数
    38     auto scopeGuard3 = make_scope_guard(Functor{});
    39     Test t;
    40     auto scopeGuard4 = make_scope_guard(std::bind(&Test::Func, &t));
    41     auto scopeGuard5 = make_scope_guard(std::bind(&Test::FuncWithParamters, &t, 1)); // bind适配器绑定一个参数 但是不能是占位符.
    42 
    43     return 0;
    44 }

      

      如果我们对其中部分对象调用dismiss方法,即可动态取消回调函数的调用:

     1 int main()
     2 {
     3     using sg::detail::make_scope_guard;
     4     // 1 普通函数
     5     auto scopeGuard1 = make_scope_guard(Rollback);
     6     // 2 Lambda表达式
     7     auto scopeGuard2 = make_scope_guard([]() {
     8         std::cout << "Lambda expression.
    ";
     9         });
    10     // 3 仿函数
    11     auto scopeGuard3 = make_scope_guard(Functor{});
    12     scopeGuard3.dismiss();   // dismiss
    13     Test t;
    14     auto scopeGuard4 = make_scope_guard(std::bind(&Test::Func, &t));
    15     auto scopeGuard5 = make_scope_guard(std::bind(&Test::FuncWithParamters, &t, 1));
    16     scopeGuard5.dismiss();  // dismiss
    17 
    18     return 0;
    19 }

      

    三、总结

      scope guard机制在很早前就被提出,只是限于当时C++语言支持度不够,实现比较复杂;但是ModernC++实现就变的非常简单。它在Folly等著名开源库中都有实现和使用,在我最近的一个项目中,就涉及到它。通过它帮我能方便地管理对象在获取资源过程中异常自动释放已经获取的资源,同时在获取资源成功后,动态dismiss自动释放资源的方法。所以,在我们学习编程的过程中,既要积极吸收新的技术,还要合理运用,这样才能让我们在今后工作中游刃有余的处理各种问题。

     参考

    1.git@github.com:ricab/scope_guard.git

    2.Generic<Programming>: Change the Way You Write Exception-Safe Code &#8212; Forever | Dr Dobb's (drdobbs.com)

  • 相关阅读:
    MIN (Transact-SQL)【转】
    ROW_NUMBER() OVER函数的基本用法用法【转】
    读取文件中的内容
    Stopwatch 和TimeSpan介绍【转】
    TimeSpan类【转】
    Stopwatch 类【转】
    ToString()使用方法
    用c#读取文件内容中文是乱码的解决方法:
    vue实现购物车和地址选配(二)
    vue实现购物车和地址选配
  • 原文地址:https://www.cnblogs.com/smartNeo/p/14891493.html
Copyright © 2011-2022 走看看