参考:https://www.cnblogs.com/chenny7/p/11990105.html
RAII的本质内容是用对象代表资源,把管理资源的任务转化为管理对象的任务,将资源的获取和释放与对象的构造和析构对应起来,从而确保在对象的生存期内资源始终有效,对象销毁时资源必被释放。换句话说,拥有对象就等于拥有资源,对象存在则资源必定存在。由此可见,RAII惯用法是进行资源管理的有力武器。
ScopeGuard
ScopeGuard 最大的用处也是释放资源。
比如分配内存,做某些操作,再释放内存,很多人会这样写:
void* data = malloc(size);
xxxxx
if (xxx) {
xxxxx
}
else {
xxxxx
}
free(data);
这样的代码是很脆弱的,有下面问题。
- malloc 和 free 可能会相隔很远,难以看出它们的对应关系。
- 另外中间有任何异常,中途 return,free 都不能被执行,就有资源泄露。
- 假如将这段代码复用,搬到另外地方,容易漏掉 free。
为解决资源释放问题,有些老旧的 C/C++ 代码,会采用折衷写法,会使用 do {} while(false)
,甚至会用到 goto。 比如:
do {
if (xxx) {
break;
}
if (xxx) {
break;
}
xxx 正常操作
return 1;
} while(false);
free(data);
xxxx 释放资源
return 0;
或者
if (xxxx) {
goto fail;
}
if (xxxx) {
goto fail;
}
xxx 正常操作
return 1;
fail:
free(data);
xxxx 释放资源
return 0;
对比 ScopeGuard 的写法:
void* data = malloc(size);
ON_SCOPE_EXIT {
free(data);
};
xxxxx
if (xxx) {
xxxxx
}
else {
xxxxx
}
资源一旦分配,接下来就立即使用 ScopeGuard 释放资源,这样分配和释放就会靠在一起,当退出作用域的的时候,里面的语句就被执行,释放掉资源。
无论下面的语句抛异常也好,中途退出也好,代码都是安全的。这样的代码也更容易修改,比如将其移动到另外的地方,它还是安全的。假如分配和释放分隔两地,移动代码时就很容易漏掉某些语句。
在Golang语言中,有defer关键字可以实现上面例子中的scope guard,而C++需要自己实现。
C++ 实现用到 C++ 11 的 lamda,定义对象将 lamda 存起来,在析构函数中调用。这个 lamda 在不同的场合也有不同的叫法,比如匿名函数,闭包,代码块,block。
有些小地方需要注意:一个是存储 lamda 使用了模板,而不是 std::function, 这个可以避免 lamda 转 std::function 的开销(尽管这个开销在绝大多数情况下可以忽略不计)。
#ifndef __SCOPE_GUARD_H__
#define __SCOPE_GUARD_H__
#define __SCOPEGUARD_CONCATENATE_IMPL(s1, s2) s1##s2
#define __SCOPEGUARD_CONCATENATE(s1, s2) __SCOPEGUARD_CONCATENATE_IMPL(s1, s2)
#if defined(__cplusplus)
#include <type_traits>
// ScopeGuard for C++11
namespace clover {
template <typename Fun>
class ScopeGuard {
public:
ScopeGuard(Fun &&f) : _fun(std::forward<Fun>(f)), _active(true) {
}
~ScopeGuard() {
if (_active) {
_fun();
}
}
void dismiss() {
_active = false;
}
ScopeGuard() = delete;
ScopeGuard(const ScopeGuard &) = delete;
ScopeGuard &operator=(const ScopeGuard &) = delete;
ScopeGuard(ScopeGuard &&rhs) : _fun(std::move(rhs._fun)), _active(rhs._active) {
rhs.dismiss();
}
private:
Fun _fun;
bool _active;
};
namespace detail {
enum class ScopeGuardOnExit {};
template <typename Fun>
inline ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun &&fn) {
return ScopeGuard<Fun>(std::forward<Fun>(fn));
}
} // namespace detail
} // namespace clover
// Helper macro
#define ON_SCOPE_EXIT \
auto __SCOPEGUARD_CONCATENATE(ext_exitBlock_, __LINE__) = clover::detail::ScopeGuardOnExit() + [&]()
#else
// ScopeGuard for Objective-C
typedef void (^ext_cleanupBlock_t)(void);
static inline void ext_executeCleanupBlock(__strong ext_cleanupBlock_t *block) {
(*block)();
}
#define ON_SCOPE_EXIT \
__strong ext_cleanupBlock_t __SCOPEGUARD_CONCATENATE(ext_exitBlock_, __LINE__) \
__attribute__((cleanup(ext_executeCleanupBlock), unused)) = ^
#endif
#endif /* __SCOPE_GUARD_H__ */