我正在尝试写一个简单的 ScopeGuard based on Alexandrescu concepts但使用 c++11 习语。
namespace RAII
{
template< typename Lambda >
class ScopeGuard
{
mutable bool committed;
Lambda rollbackLambda;
public:
ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}
template< typename AdquireLambda >
ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)
{
_al();
}
~ScopeGuard()
{
if (!committed)
rollbackLambda();
}
inline void commit() const { committed = true; }
};
template< typename aLambda , typename rLambda>
const ScopeGuard< rLambda >& makeScopeGuard( const aLambda& _a , const rLambda& _r)
{
return ScopeGuard< rLambda >( _a , _r );
}
template<typename rLambda>
const ScopeGuard< rLambda >& makeScopeGuard(const rLambda& _r)
{
return ScopeGuard< rLambda >(_r );
}
}
用法如下:
void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptions()
{
std::vector<int> myVec;
std::vector<int> someOtherVec;
myVec.push_back(5);
//first constructor, adquire happens elsewhere
const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );
//sintactically neater, since everything happens in a single line
const auto& b = RAII::makeScopeGuard( [&]() { someOtherVec.push_back(42); }
, [&]() { someOtherVec.pop_back(); } );
b.commit();
a.commit();
}
由于我的版本比那里的大多数示例(如 Boost ScopeExit)短得多,我想知道我遗漏了哪些专业。希望我在这里处于 80/20 的场景中(我得到了 80% 的整洁度和 20% 的代码行),但我不禁想知道我是否遗漏了一些重要的东西,或者是否有一些缺点值得提到这个版本的 ScopeGuard 成语
谢谢!
编辑 我注意到 makeScopeGuard 的一个非常重要的问题,它在构造函数中采用了 adquire lambda。如果 adquire lambda 抛出,则永远不会调用 release lambda,因为范围保护从未完全构造。在许多情况下,这是所需的行为,但我觉得有时也需要一个在抛出发生时调用回滚的版本:
//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
auto scope = ScopeGuard< rLambda >(std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
_a();
return scope;
}
所以为了完整起见,我想把完整的代码放在这里,包括测试:
#include <vector>
namespace RAII
{
template< typename Lambda >
class ScopeGuard
{
bool committed;
Lambda rollbackLambda;
public:
ScopeGuard( const Lambda& _l) : committed(false) , rollbackLambda(_l) {}
ScopeGuard( const ScopeGuard& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda)
{
if (_sc.committed)
committed = true;
else
_sc.commit();
}
ScopeGuard( ScopeGuard&& _sc) : committed(false) , rollbackLambda(_sc.rollbackLambda)
{
if (_sc.committed)
committed = true;
else
_sc.commit();
}
//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
template< typename AdquireLambda >
ScopeGuard( const AdquireLambda& _al , const Lambda& _l) : committed(false) , rollbackLambda(_l)
{
std::forward<AdquireLambda>(_al)();
}
//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
template< typename AdquireLambda, typename L >
ScopeGuard( AdquireLambda&& _al , L&& _l) : committed(false) , rollbackLambda(std::forward<L>(_l))
{
std::forward<AdquireLambda>(_al)(); // just in case the functor has &&-qualified operator()
}
~ScopeGuard()
{
if (!committed)
rollbackLambda();
}
inline void commit() { committed = true; }
};
//WARNING: only safe if adquire lambda does not throw, otherwise release lambda is never invoked, because the scope guard never finished initialistion..
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
return ScopeGuard< rLambda >( std::forward<aLambda>(_a) , std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
}
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuardThatDoesRollbackIfAdquireThrows( aLambda&& _a , rLambda&& _r) // again perfect forwarding
{
auto scope = ScopeGuard< rLambda >(std::forward<rLambda>(_r )); // *** no longer UB, because we're returning by value
_a();
return scope;
}
template<typename rLambda>
ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
{
return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
}
namespace basic_usage
{
struct Test
{
std::vector<int> myVec;
std::vector<int> someOtherVec;
bool shouldThrow;
void run()
{
shouldThrow = true;
try
{
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
} catch (...)
{
AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");
}
shouldThrow = false;
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows();
AssertMsg( myVec.size() == 1 && someOtherVec.size() == 1 , "unexpected end state");
shouldThrow = true;
myVec.clear(); someOtherVec.clear();
try
{
SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows();
} catch (...)
{
AssertMsg( myVec.size() == 0 && someOtherVec.size() == 0 , "rollback did not work");
}
}
void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesNOTRollbackIfAdquireThrows() //throw()
{
myVec.push_back(42);
auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );
auto b = RAII::makeScopeGuardThatDoesNOTRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); }
, [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );
if (shouldThrow) throw 1;
b.commit();
a.commit();
}
void SomeFuncThatShouldBehaveAtomicallyInCaseOfExceptionsUsingScopeGuardsThatDoesRollbackIfAdquireThrows() //throw()
{
myVec.push_back(42);
auto a = RAII::makeScopeGuard( [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty myVec"); myVec.pop_back(); } );
auto b = RAII::makeScopeGuardThatDoesRollbackIfAdquireThrows( [&]() { someOtherVec.push_back(42); if (shouldThrow) throw 1; }
, [&]() { HAssertMsg( myVec.size() > 0 , "attempt to call pop_back() in empty someOtherVec"); someOtherVec.pop_back(); } );
b.commit();
a.commit();
}
};
}
}
最佳答案
更短:我不知道你们为什么坚持将模板放在 guard 类中。
#include <functional>
class scope_guard {
public:
template<class Callable>
scope_guard(Callable && undo_func) try : f(std::forward<Callable>(undo_func)) {
} catch(...) {
undo_func();
throw;
}
scope_guard(scope_guard && other) : f(std::move(other.f)) {
other.f = nullptr;
}
~scope_guard() {
if(f) f(); // must not throw
}
void dismiss() noexcept {
f = nullptr;
}
scope_guard(const scope_guard&) = delete;
void operator = (const scope_guard&) = delete;
private:
std::function<void()> f;
};
请注意,清理代码必须不抛出,否则您会遇到与抛出析构函数类似的情况。
用法:
// do step 1
step1();
scope_guard guard1 = [&]() {
// revert step 1
revert1();
};
// step 2
step2();
guard1.dismiss();
我的灵感是一样的DrDobbs article至于OP。
编辑 2017/2018:观看(部分)后Andrei's presentation安德烈链接到的(我跳到最后说“痛苦地接近理想!”)我意识到这是可行的。大多数时候,您不想为所有事情都配备额外的 guard 。你只是做一些事情,最后它要么成功,要么应该发生回滚。
2018 年编辑:添加了执行策略,消除了 dismiss
调用的必要性。
#include <functional>
#include <deque>
class scope_guard {
public:
enum execution { always, no_exception, exception };
scope_guard(scope_guard &&) = default;
explicit scope_guard(execution policy = always) : policy(policy) {}
template<class Callable>
scope_guard(Callable && func, execution policy = always) : policy(policy) {
this->operator += <Callable>(std::forward<Callable>(func));
}
template<class Callable>
scope_guard& operator += (Callable && func) try {
handlers.emplace_front(std::forward<Callable>(func));
return *this;
} catch(...) {
if(policy != no_exception) func();
throw;
}
~scope_guard() {
if(policy == always || (std::uncaught_exception() == (policy == exception))) {
for(auto &f : handlers) try {
f(); // must not throw
} catch(...) { /* std::terminate(); ? */ }
}
}
void dismiss() noexcept {
handlers.clear();
}
private:
scope_guard(const scope_guard&) = delete;
void operator = (const scope_guard&) = delete;
std::deque<std::function<void()>> handlers;
execution policy = always;
};
用法:
scope_guard scope_exit, scope_fail(scope_guard::execution::exception);
action1();
scope_exit += [](){ cleanup1(); };
scope_fail += [](){ rollback1(); };
action2();
scope_exit += [](){ cleanup2(); };
scope_fail += [](){ rollback2(); };
// ...
关于c++ - 最简单最整洁的c++11 ScopeGuard,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10270328/