Я пытаюсь написать простой ScopeGuard на основе концепций Alexandrescu, но с С++ 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, которая принимает запрос lambda в конструкторе. Если стрелка лямбда adquire, то освобождающая лямбда никогда не вызывается, потому что защитная рамка никогда не была полностью построена. Во многих случаях это желаемое поведение, но я чувствую, что иногда может потребоваться версия, которая будет вызывать откат, если произойдет бросок:
//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();
}
};
}
}