私は単純な Alexandrescuの概念に基づいたScopeGuard を記述しようとしていますが、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のシナリオ(コード行の20%で80%の清gotさを得た)にいることを願っていますが、重要なものが欠けているのか、それとも何らかの価値があるのか疑問に思わずにはいられませんScopeGuardイディオムのこのバージョンの言及
ありがとう!
Editコンストラクタでadquire lambdaを使用するmakeScopeGuardの非常に重要な問題に気付きました。 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();
}
};
}
}
Boost.ScopeExitは、非C++ 11コード、つまり言語のラムダにアクセスできないコードで動作する必要があるマクロです。巧妙なテンプレートハックを使用します(<
テンプレートと比較演算子の両方!)およびラムダ機能をエミュレートするプリプロセッサ。そのため、コードが長くなります。
また、示されているコードはバグがあります(おそらく、既存のソリューションを使用する最も強力な理由です)。一時的な参照を返すため、未定義の動作を呼び出します。
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 ));
}
さらに短く:なぜあなたは、皆さんがテンプレートをガードクラスに置くことを主張するのかわかりません。
#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の記事 でした。
編集2017/2018:(一部) Andreiのプレゼンテーション Andréがリンクしたことを確認した後(「理想に近い痛みを伴う!」ほとんどの場合、すべてに余分なガードを付けたくありません。あなたはただ何かをするだけで、最終的には成功するかロールバックが起こるはずです。
編集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(); };
// ...
これを見ることに興味があるかもしれません presentation Andrei自身がc ++ 11でscopedguardを改善する方法について考えた
RAIIパターンを実装する目的で_std::unique_ptr
_を使用できます。例えば:
_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
_のdeleter関数は、例外がアクティブな間にスコープが残った場合、以前に挿入された値をロールバックします。明示的なコミットを希望する場合は、deleter関数のuncaugth_exception()
質問を削除し、ポインターを解放するp.release()
ブロックの最後に追加できます。 デモ を参照してください。
私はこれをチャームのように使用します。余分なコードはありません。
shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
このアプローチは、提案を通じてC++ 17またはLibrary Fundamentals TSで標準化される可能性があります P0052R
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
と同じ警告になります。戻り値を保存する必要があるか、デストラクタがすぐに呼び出されて、期待どおりに機能しないためです。
makeScopeGuardはconst参照を返します。次のような行で、このconst参照を呼び出し側のconst refに保存することはできません。
const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } );
したがって、未定義の動作を呼び出しています。
Herb Sutter GOTW 88 は、値をconst参照に保存するための背景を提供します。
コミットメントトラッキングはありませんが、非常に簡潔で高速です。
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
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()
および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
あなたはすでに答えを選んでいますが、とにかく挑戦します:
#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
ingがロールバックをトリガーするかどうかを示すブール値パラメーターがあります。
std::decay
partは、cv修飾子と参照マーカーの両方を取り除きます。ただし、入力型が組み込み配列である場合、配列からポインターへの変換も適用されるため、この汎用目的には使用できません。
まだ別の答えがありますが、私は他の人が何らかの形で欠けているのを見つけるのが怖いです。特に、受け入れられた回答の日付は2012年ですが、重要なバグがあります( このコメント を参照)。これは、テストの重要性を示しています。
ここ は、C++ 11 scope_guardの実装であり、公開されており、広範囲にテストされています。それは以下のことを意味します:
std::function
または仮想テーブルのペナルティ)機能の全リスト も参照してください。
もう1つ、@ 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 ) );