webentwicklung-frage-antwort-db.com.de

Der einfachste und sauberste C++ 11 ScopeGuard

Ich versuche einen einfachen ScopeGuard basierend auf Alexandrescu-Konzepten zu schreiben aber mit C++ 11-Idiomen. 

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 );
    }
}

Hier ist die Verwendung:

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();
}

Da meine Version viel kürzer ist als die meisten anderen Beispiele (wie Boost ScopeExit), frage ich mich, welche Spezialitäten ich auslasse. Ich befinde mich hoffentlich in einem 80/20-Szenario (wo ich mit 20 Prozent Codezeilen 80 Prozent ordentlich geworden bin), aber ich kann mir nicht helfen, mich zu fragen, ob mir etwas Wichtiges fehlt oder ob es einen Mangel gibt Erwähnung dieser Version des ScopeGuard-Idioms

vielen Dank!

Edit Ich habe ein sehr wichtiges Problem mit dem makeScopeGuard festgestellt, das den adquire lambda im Konstruktor übernimmt. Wenn das angefragte Lambda wirft, wird das Release-Lambda nie aufgerufen, da der Scope Guard nie vollständig aufgebaut wurde. In vielen Fällen ist dies das gewünschte Verhalten, aber ich glaube, dass manchmal auch eine Version gewünscht wird, die ein Rollback auslöst, wenn ein Wurf erfolgt:

//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;
}

der Vollständigkeit halber möchte ich hier den vollständigen Code einschließlich der Tests einfügen:


#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();
            }
        };
    }
}
29
lurscher

Boost.ScopeExit ist ein Makro, das mit Nicht-C++ 11-Code arbeiten muss, d. H. Code, der keinen Zugriff auf Lambdas in der Sprache hat. Es verwendet einige clevere Template-Hacks (wie den Missbrauch der Mehrdeutigkeit, die sich aus der Verwendung von < für Templates und Vergleichsoperatoren ergibt!) Und dem Präprozessor zur Emulation von Lambda-Features. Deshalb ist der Code länger.

Der gezeigte Code ist auch fehlerhaft (was wahrscheinlich der stärkste Grund für die Verwendung einer vorhandenen Lösung ist): Er ruft undefined Verhalten auf, weil Verweise auf temporäre Dateien zurückgegeben werden.

Da Sie versuchen, C++ 11-Features zu verwenden, kann der Code durch die Verwendung von Verschiebungssemantiken, rvalue-Referenzen und Perfect Forwarding erheblich verbessert werden:

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 ));
}
21

Noch kürzer: Ich weiß nicht, warum Sie darauf bestehen, die Vorlage in die Klasse der Wächter aufzunehmen.

#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;
};

Es ist wichtig, dass der Bereinigungscode nicht ausgelöst wird. Andernfalls treten Sie in ähnlichen Situationen auf wie mit dem Zerstören von Destruktoren.

Verwendungszweck:

// do step 1
step1();
scope_guard guard1 = [&]() {
    // revert step 1
    revert1();
};

// step 2
step2();
guard1.dismiss();

Meine Inspiration war der gleiche DrDobbs-Artikel wie für das OP.


Edit 2017/2018: Nachdem ich (einige von) Andrei's Präsentation gesehen hatte, mit der André verlinkt hat (ich habe bis zum Ende gesprungen, wo es "Schmerzhaft nah an dem Ideal!" Stand), wurde mir klar, dass es machbar ist. Die meiste Zeit möchten Sie keine zusätzlichen Wachen für alles haben. Du machst einfach Sachen und am Ende ist es entweder erfolgreich oder ein Rollback sollte stattfinden. 

Edit 2018: Es wurde eine Ausführungsrichtlinie hinzugefügt, durch die der Aufruf von dismiss nicht mehr erforderlich war.

#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;
};

Verwendungszweck: 

scope_guard scope_exit, scope_fail(scope_guard::execution::exception);

action1();
scope_exit += [](){ cleanup1(); };
scope_fail += [](){ rollback1(); };

action2();
scope_exit += [](){ cleanup2(); };
scope_fail += [](){ rollback2(); };

// ...
27
Fozi

Vielleicht interessiert es Sie, diese Präsentation von Andrei selbst zu sehen, wie Sie Scopedguard mit c ++ 11 verbessern können

14
André

Sie können std::unique_ptr für diesen Zweck verwenden, der das RAII-Muster implementiert . Zum Beispiel:

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

Die Deleter-Funktion aus dem unique_ptr p rollt den zuvor eingefügten Wert zurück, wenn der Gültigkeitsbereich verlassen wurde, während eine Ausnahme aktiv ist. Wenn Sie ein explizites Commit bevorzugen, können Sie die uncaugth_exception()-Frage in der Deleter-Funktion entfernen und am Ende des Blocks p.release() hinzufügen, wodurch der Zeiger freigegeben wird. Siehe Demo hier.

10
kwarnke

Es besteht die Möglichkeit, dass dieser Ansatz in C++ 17 oder in den Bibliotheksgrundlagen TS durch Vorschlag/standardisiert wird. 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;

Auf den ersten Blick hat dies die gleiche Einschränkung wie std::async, da Sie den Rückgabewert speichern müssen, da der Destruktor sofort aufgerufen wird und nicht wie erwartet funktioniert. 

6
Erik van Velzen

makeScopeGuard gibt eine const-Referenz zurück. Sie können diese const-Referenz nicht in einer const ref auf der Seite des Aufrufers in einer Zeile wie: speichern:

const auto& a = RAII::makeScopeGuard( [&]() { myVec.pop_back(); } ); 

Sie rufen also undefiniertes Verhalten auf.

Herb Sutter GOTW 88 gibt Hintergrundinformationen zum Speichern von Werten in const-Referenzen.

3
mirk

Ich benutze das funktioniert wie ein Zauber, kein zusätzlicher Code.

shared_ptr<int> x(NULL, [&](int *) { CloseResource(); });
3
stu

Ohne Commitment Tracking, aber extrem ordentlich und schnell.

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;})

Verwendungszweck

{
    puts("a");
    auto _ = makeScopeExit([]() { puts("b"); });
    // More readable with a macro
    ON_SCOPE_EXIT(puts("c"));
} # prints a, c, b
2
ens

FWIW Ich denke, dass Andrei Alexandrescu in seinem CppCon 2015-Vortrag über "Declarative Control Flow" ( Video , Folien ) eine ziemlich ordentliche Syntax verwendet hat.

Der folgende Code ist stark davon inspiriert:

Try It OnlineGitHub Gist

#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"

Für Ihren Anwendungsfall könnten Sie auch interessiert sein an std::uncaught_exception() und std::uncaught_exceptions() , um zu erfahren, ob Sie den Gültigkeitsbereich "normal" verlassen oder nachdem eine Ausnahme ausgelöst wurde:

ON_SCOPE_EXIT [] {
    if (std::uncaught_exception()) {
        cout << "an exception has been thrown" << endl;
    }
    else {
        cout << "we're probably ok" << endl;
    }
};

HTH

1
maddouri

Sie haben bereits eine Antwort ausgewählt, aber ich nehme die Herausforderung trotzdem an:

#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.

Anstatt über Erstellungsfunktionen und einen Konstruktor zu verfügen, sind Sie auf die Erstellungsfunktionen beschränkt, wobei der Hauptkonstruktor private ist. Ich konnte nicht herausfinden, wie ich die Instantiierungen friended auf diejenigen beschränken kann, die den aktuellen Template-Parameter betreffen. (Möglicherweise, weil der Parameter nur im Rückgabetyp erwähnt wird.) Möglicherweise kann auf dieser Website eine Korrektur dazu angefordert werden. Da die erste Aktion nicht gespeichert werden muss, ist sie nur in den Erstellungsfunktionen vorhanden. Es gibt einen booleschen Parameter, der gekennzeichnet werden soll, wenn throwing von der ersten Aktion ein Rollback auslöst oder nicht.

Der std::decay-Teil entfernt sowohl cv-Qualifiers als auch Referenzmarken. Sie können es jedoch nicht für diesen allgemeinen Zweck verwenden, wenn der Eingabetyp ein integriertes Array ist, da er auch die Konvertierung von Array zu Zeiger anwendet.

0
CTMacUser

Noch eine Antwort, aber ich fürchte, die anderen fehlen auf die eine oder andere Art. Die akzeptierte Antwort stammt aus dem Jahr 2012, aber sie hat einen wichtigen Fehler (siehe diesen Kommentar ). Dies zeigt die Wichtigkeit des Testens.

Hier ist eine Implementierung von a> = C++ 11 scope_guard, die offen verfügbar ist und ausführlich getestet wurde. Es soll sein/haben:

  • modern, elegant, einfach (meistens Einzelfunktionsschnittstelle und keine Makros)
  • allgemein (akzeptiert alle anrufbaren, die die Voraussetzungen erfüllen)
  • sorgfältig dokumentiert
  • thin Callback-Wrapping (kein std::function oder Strafen für virtuelle Tabellen)
  • richtige Ausnahmespezifikationen

Siehe auch die vollständige Liste der Funktionen .

0
ricab

Hier ist eine andere, jetzt eine Variation von @ 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 ) );
0
Tarc