gpt4 book ai didi

c++ - 最简单最整洁的c++11 ScopeGuard

转载 作者:IT老高 更新时间:2023-10-28 12:39:59 36 4
gpt4 key购买 nike

我正在尝试写一个简单的 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/

36 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com