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\n"; 4 } 5 6 struct Functor 7 { 8 void operator()() 9 { 10 std::cout << "Functor\n"; 11 } 12 }; 13 14 class Test 15 { 16 public: 17 void Func() 18 { 19 std::cout << "Test::Func\n"; 20 } 21 void FuncWithParamters(int a) 22 { 23 std::cout << "Test::FuncWithParamters(" << a << ")\n"; 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.\n"; 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.\n"; 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自动释放资源的方法。所以,在我们学习编程的过程中,既要积极吸收新的技术,还要合理运用,这样才能让我们在今后工作中游刃有余的处理各种问题。
参考:git@github.com:ricab/scope_guard.git