我正在尝试编写一个简单的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的一个非常重要的问题,它在构造函数中获取了获取lambda。如果adquire lambda抛出,则释放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();
}
};
}
}
答案 0 :(得分:28)
更短:我不知道为什么你们坚持把模板放在后卫班上。
#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();
我的灵感与OP相同DrDobbs article。
编辑2017/2018:看完André链接到的Andrei's presentation之后(我跳到最后说“痛苦接近理想!”)我意识到这是可行的。大多数时候你不想为所有事情提供额外的警卫。你只需做一些事情,最后它要么成功要么应该回滚。
编辑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(); };
// ...
答案 1 :(得分:21)
Boost.ScopeExit是一个需要使用非C ++ 11代码的宏,即无法访问语言中lambdas的代码。它使用了一些聪明的模板黑客(比如滥用模糊和比较运算符使用<
产生的歧义!)和模拟lambda特征的预处理器。这就是代码更长的原因。
显示的代码也有错误(这可能是使用现有解决方案的最有力的理由):由于返回对临时数据库的引用而调用未定义的行为。
由于您正在尝试使用C ++ 11功能,因此可以通过使用移动语义,右值引用和完美转发来大大改进代码:
template< typename Lambda >
class ScopeGuard
{
bool committed; // not mutable
Lambda rollbackLambda;
public:
// make sure this is not a copy ctor
template <typename L,
DisableIf<std::is_same<RemoveReference<RemoveCv<L>>, ScopeGuard<Lambda>>> =_
>
/* see http://loungecpp.net/w/EnableIf_in_C%2B%2B11
* and http://stackoverflow.com/q/10180552/46642 for info on DisableIf
*/
explicit ScopeGuard(L&& _l)
// explicit, unless you want implicit conversions from *everything*
: committed(false)
, rollbackLambda(std::forward<L>(_l)) // avoid copying unless necessary
{}
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()
}
// move constructor
ScopeGuard(ScopeGuard&& that)
: committed(that.committed)
, rollbackLambda(std::move(that.rollbackLambda)) {
that.committed = true;
}
~ScopeGuard()
{
if (!committed)
rollbackLambda(); // what if this throws?
}
void commit() { committed = true; } // no need for const
};
template< typename aLambda , typename rLambda>
ScopeGuard< rLambda > // return by value is the preferred C++11 way.
makeScopeGuard( 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 rLambda>
ScopeGuard< rLambda > makeScopeGuard(rLambda&& _r)
{
return ScopeGuard< rLambda >( std::forward<rLambda>(_r ));
}
答案 2 :(得分:14)
你可能有兴趣看到安德烈本人亲自看到presentation如何用c ++ 11改进scopedguard
答案 3 :(得分:10)
您可以使用std::unique_ptr
来实现RAII模式。
例如:
vector<int> v{};
v.push_back(42);
unique_ptr<decltype(v), function<void(decltype(v)*)>>
p{&v, [] (decltype(v)* v) { if (uncaught_exception()) { v->pop_back(); }}};
throw exception(); // rollback
p.release(); // explicit commit
如果在异常处于活动状态时保留范围,则unique_ptr p
的删除函数会将之前插入的值回滚。如果您更喜欢显式提交,则可以删除删除函数中的uncaugth_exception()
问题,并在释放指针的块p.release()
的末尾添加。请在此处查看Demo。
答案 4 :(得分:6)
这种方法有可能在C ++ 17或图书馆基础知识TS中通过提案P0052R0进行标准化
template <typename EF>
scope_exit<see below> make_scope_exit(EF &&exit_function) noexcept;
template <typename EF>
scope_exit<see below> make_scope_fail(EF && exit_function) noexcept;
template <typename EF>
scope_exit<see below> make_scope_success(EF && exit_function) noexcept;
乍一看,这与std::async
具有相同的警告,因为您必须存储返回值,否则将立即调用析构函数,它将无法按预期工作。
答案 5 :(得分:5)
大多数其他解决方案都包含std::function
的lambda,例如通过使用lambda参数初始化template<typename F>
struct OnExit
{
F func;
OnExit(F&& f): func(std::forward<F>(f)) {}
~OnExit() { func(); }
};
template<typename F> OnExit(F&& frv) -> OnExit<F>;
int main()
{
auto func = []{ };
OnExit x(func); // No move, F& refers to func
OnExit y([]{}); // Lambda is moved to F.
}
或从lambda推断出的类型的对象。
这是一个非常简单的代码,它允许使用命名的lambda而不移动它(需要C ++ 17):
kubectl edit deployment deploymentname -n namespacename
当参数为左值时,推导指南将F推导为左值引用。
答案 6 :(得分:4)
我使用它就像一个魅力,没有额外的代码。
shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
答案 7 :(得分:3)
makeScopeGuard返回一个const引用。你不能将这个const引用存储在调用者一侧的const ref中,如下所示:
const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );
所以你正在调用未定义的行为。
Herb Sutter GOTW 88 给出了关于在const引用中存储值的一些背景知识。
答案 8 :(得分:2)
没有承诺跟踪,但非常整洁和快速。
template <typename F>
struct ScopeExit {
ScopeExit(F&& f) : m_f(std::forward<F>(f)) {}
~ScopeExit() { m_f(); }
F m_f;
};
template <typename F>
ScopeExit<F> makeScopeExit(F&& f) {
return ScopeExit<F>(std::forward<F>(f));
};
#define STRING_JOIN(arg1, arg2) STRING_JOIN2(arg1, arg2)
#define STRING_JOIN2(arg1, arg2) arg1 ## arg2
#define ON_SCOPE_EXIT(code) auto STRING_JOIN(scopeExit, __LINE__) = makeScopeExit([&](){code;})
用法
{
puts("a");
auto _ = makeScopeExit([]() { puts("b"); });
// More readable with a macro
ON_SCOPE_EXIT(puts("c"));
} # prints a, c, b
答案 9 :(得分:0)
你已经选择了答案,但无论如何我都会接受挑战:
#include <iostream>
#include <type_traits>
#include <utility>
template < typename RollbackLambda >
class ScopeGuard;
template < typename RollbackLambda >
auto make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard<typename
std::decay<RollbackLambda>::type>;
template < typename RollbackLambda >
class ScopeGuard
{
// The input may have any of: cv-qualifiers, l-value reference, or both;
// so I don't do an exact template match. I want the return to be just
// "ScopeGuard," but I can't figure it out right now, so I'll make every
// version a friend.
template < typename AnyRollbackLambda >
friend
auto make_ScopeGuard( AnyRollbackLambda && ) -> ScopeGuard<typename
std::decay<AnyRollbackLambda>::type>;
public:
using lambda_type = RollbackLambda;
private:
// Keep the lambda, of course, and if you really need it at the end
bool committed;
lambda_type rollback;
// Keep the main constructor private so regular creation goes through the
// external function.
explicit ScopeGuard( lambda_type rollback_action )
: committed{ false }, rollback{ std::move(rollback_action) }
{}
public:
// Do allow moves
ScopeGuard( ScopeGuard &&that )
: committed{ that.committed }, rollback{ std::move(that.rollback) }
{ that.committed = true; }
ScopeGuard( ScopeGuard const & ) = delete;
// Cancel the roll-back from being called.
void commit() { committed = true; }
// The magic happens in the destructor.
// (Too bad that there's still no way, AFAIK, to reliably check if you're
// already in exception-caused stack unwinding. For now, we just hope the
// roll-back doesn't throw.)
~ScopeGuard() { if (not committed) rollback(); }
};
template < typename RollbackLambda >
auto make_ScopeGuard( RollbackLambda &&r ) -> ScopeGuard<typename
std::decay<RollbackLambda>::type>
{
using std::forward;
return ScopeGuard<typename std::decay<RollbackLambda>::type>{
forward<RollbackLambda>(r) };
}
template < typename ActionLambda, typename RollbackLambda >
auto make_ScopeGuard( ActionLambda && a, RollbackLambda &&r, bool
roll_back_if_action_throws ) -> ScopeGuard<typename
std::decay<RollbackLambda>::type>
{
using std::forward;
if ( not roll_back_if_action_throws ) forward<ActionLambda>(a)();
auto result = make_ScopeGuard( forward<RollbackLambda>(r) );
if ( roll_back_if_action_throws ) forward<ActionLambda>(a)();
return result;
}
int main()
{
auto aa = make_ScopeGuard( []{std::cout << "Woah" << '\n';} );
int b = 1;
try {
auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, true );
} catch (...) {}
std::cout << b++ << '\n';
try {
auto bb = make_ScopeGuard( [&]{b *= 2; throw b;}, [&]{b = 0;}, false );
} catch (...) {}
std::cout << b++ << '\n';
return 0;
}
// Should write: "0", "2", and "Woah" in that order on separate lines.
而不是创建函数和构造函数,只限于创建函数,主构造函数为private
。我无法弄清楚如何将friend
- ed实例限制为仅涉及当前模板参数的实例。 (可能因为参数仅在返回类型中提及。)也许可以在此站点上对此进行修复。由于不需要存储第一个动作,因此它仅存在于创建函数中。如果throw
从第一个动作触发回滚,则有一个布尔参数来标记。
std::decay
部分同时剥离cv限定符和参考标记。但是如果输入类型是内置数组,则不能将它用于此通用目的,因为它也将应用数组到指针的转换。
答案 10 :(得分:0)
这是另一个,现在是@ kwarnke的一个变体:
std::vector< int > v{ };
v.push_back( 42 );
auto guard_handler =
[ & v ] ( nullptr_t ptr )
{
v.pop_back( );
};
std::shared_ptr< decltype( guard_handler ) > guard( nullptr , std::move( guard_handler ) );
答案 11 :(得分:0)
还有另一个答案,但恐怕我发现其他所有人都缺少一种或另一种方式。值得注意的是,接受的答案可以追溯到2012年,但是它有一个重要的错误(请参见this comment)。这说明了测试的重要性。
Here是> = C ++ 11 scope_guard的实现,该实现公开可用并经过广泛测试。它应该是/具有:
std::function
或虚拟表惩罚)答案 12 :(得分:0)
FWIW,我认为Andrei Alexandrescu在CppCon 2015的有关“声明式控制流”(video,slides)的演讲中使用了非常简洁的语法。
以下代码受其启发:
#include <iostream>
#include <type_traits>
#include <utility>
using std::cout;
using std::endl;
template <typename F>
struct ScopeExitGuard
{
public:
struct Init
{
template <typename G>
ScopeExitGuard<typename std::remove_reference<G>::type>
operator+(G&& onScopeExit_)
{
return {false, std::forward<G>(onScopeExit_)};
}
};
private:
bool m_callOnScopeExit = false;
mutable F m_onScopeExit;
public:
ScopeExitGuard() = delete;
template <typename G> ScopeExitGuard(const ScopeExitGuard<G>&) = delete;
template <typename G> void operator=(const ScopeExitGuard<G>&) = delete;
template <typename G> void operator=(ScopeExitGuard<G>&&) = delete;
ScopeExitGuard(const bool callOnScopeExit_, F&& onScopeExit_)
: m_callOnScopeExit(callOnScopeExit_)
, m_onScopeExit(std::forward<F>(onScopeExit_))
{}
template <typename G>
ScopeExitGuard(ScopeExitGuard<G>&& other)
: m_callOnScopeExit(true)
, m_onScopeExit(std::move(other.m_onScopeExit))
{
other.m_callOnScopeExit = false;
}
~ScopeExitGuard()
{
if (m_callOnScopeExit)
{
m_onScopeExit();
}
}
};
#define ON_SCOPE_EXIT_GUARD_VAR_2(line_num) _scope_exit_guard_ ## line_num ## _
#define ON_SCOPE_EXIT_GUARD_VAR(line_num) ON_SCOPE_EXIT_GUARD_VAR_2(line_num)
// usage
// ON_SCOPE_EXIT <callable>
//
// example
// ON_SCOPE_EXIT [] { cout << "bye" << endl; };
#define ON_SCOPE_EXIT \
const auto ON_SCOPE_EXIT_GUARD_VAR(__LINE__) \
= ScopeExitGuard<void*>::Init{} + /* the trailing '+' is the trick to the call syntax ;) */
int main()
{
ON_SCOPE_EXIT [] {
cout << "on scope exit 1" << endl;
};
ON_SCOPE_EXIT [] {
cout << "on scope exit 2" << endl;
};
cout << "in scope" << endl; // "in scope"
}
// "on scope exit 2"
// "on scope exit 1"
对于您的用例,您可能还对std::uncaught_exception()
and std::uncaught_exceptions()
感兴趣,以了解您是“正常”退出范围还是在引发异常后退出:
ON_SCOPE_EXIT [] {
if (std::uncaught_exception()) {
cout << "an exception has been thrown" << endl;
}
else {
cout << "we're probably ok" << endl;
}
};
HTH