Resourceholder v2



  • Ich hatte vor einer Weile mit einem Resourceholder gespielt, aber es hat wenig Anklang gefunden.
    Jetzt habe ich mehr Plan von Lambdas und mir ist das hier über den Weg gelaufen:

    //RAII.h
    #include <functional>
    
    struct RAII{
    	RAII(std::function<void()> function) : f(std::forward<std::function<void()>>(function)){
    	}
    	~RAII(){
    		if (f)
    			f();
    	}
    	void cancel(){
    		f = 0;
    	}
    private:
    	std::function<void()> f;
    	RAII(RAII &&);
    	RAII(const RAII &);
    	RAII &operator= (RAII &&);
    	RAII &operator= (const RAII &);
    };
    
    //Use case:
    #include <iostream>
    #include "RAII.h"
    
    int main(){
        //gib eine Fehlermeldung aus wenn was schief geht
        RAII printErrormessage([]{std::cout << "something went wrong in " << __FILE__ << ':' << __FUNCTION__ << '\n';});
        Resource *resource = createGenericResource();
        if (!resource)
            return -1; //gibt "something went wrong ..." aus!
        //gib resource frei wenn was schief geht
        RAII resourceHolder([&]{freeGenericResource(resource);});
        doThingsThatMayThrowAnException(resource);
        //nichts ist schief gegangen
        printErrormessage.cancel();
    }
    

    Warum hatte das damals niemand erwähnt, wo ich doch explizit nach einer Lambdavariante gefragt hatte?

    Ich bin mir nicht sicher, ob std::forward sinnvoll ist und es das Richtige tut. Perfect forwarding hin oder her, er muss eh in jedem Fall genau einen Pointer schreiben, oder?
    Weiterhin habe ich es mit den privaten assignment-Operatoren und copy-Konstruktoren sicher übertrieben, wie sieht die kürzeste Variante aus die die Defaultvarianten privat macht?
    Wahrscheinlich könnte man zumindest die R-Value-Varianten von Copy und Assignment lassen und die RAIIs zum Beispiel in einen std::vector tun.
    Man kann noch ein Macro bauen, dass die RAII-Objekte per __LINE__ automatisch benamst™ und das die doppelten Klammern und möglicherweise das [&] überflüssig macht.

    Ich weiß, dass es noch ein Problem gibt, wenn wegen einer Exception das RAII-Objekt zerstört wird und das Lambda eine Exception wirft, weil dann das Programm sofort beendet wird. Einfach alle Exceptions zu fangen, wie es üblich zu sein scheint, erscheint aber auch keine gute Idee zu sein. Ich habe noch zu wenig Plan von Exceptions 😞

    RFC



  • Warum hatte das damals niemand erwähnt, wo ich doch explizit nach einer Lambdavariante gefragt hatte?

    Scopeguards wurden genannt oder worauf willst du hinaus? Hier die Impl: https://github.com/facebook/folly/blob/master/folly/ScopeGuard.h Einziger Unterschied, es wird ein bool benutzt, um zu testen, ob die Funktion im Destruktor ausgefuehrt wird.

    wenn wegen einer Exception das RAII-Objekt zerstört wird und das Lambda eine Exception wirft

    Darauf hat der Nutzer der Klasse selbst zu achten.



  • nwp3 schrieb:

    Warum hatte das damals niemand erwähnt, wo ich doch explizit nach einer Lambdavariante gefragt hatte?

    Weil std::function = laahm
    (Versuche mal, eine std::function zu implementieren, die mit Lambdas, Funktionszeigern und Funktionsobjekten funktioniert - das geht nur mit new + virtuellen Funktionsaufrufen = Lahm). Hier müsste ein std::move hin, kein std::forward.

    Aber wenn du den Overhead nicht willst, musst du es so machen:

    template <typename F>
    struct RAII{
        RAII(F&& function) : released(false), f(std::move(function)) {}
        ~RAII(){
            if (!released)
                f();
        }
        void cancel(){
            released = true;
        }
        RAII(RAII&& o) : released(o.released), f(std::move(o.f)) { o.released=true; }
    
        RAII(const RAII &) =delete;
        RAII &operator= (const RAII&); // operator=RAII&& automatisch deleted
    private:
        bool released;
        F f;
    };
    

    Jetzt wird die Nutzung allerdings mühsam:

    auto release = []{std::cout << "something went wrong in " << __FILE__ << ':' << __FUNCTION__ << '\n';}
    RAII<decltype(release)> printErrormessage(release);
    

    Gut, machen wir eine Helperfunktion:

    template <typename F>
    RAII<F> raii(F&& f) {
      return RAII<typename std::decay<F>::type>(std::forward<F>(f));
    }
    
    auto printErrormessage = raii([]{std::cout << "something went wrong in " << __FILE__ << ':' << __FUNCTION__ << '\n';});
    

    Dieses Teil zu benennen ist leicht sinnlos. Also in ein Makro wrappen:

    #define SCOPE_EXIT(f) \
      auto _scope_guard_## __COUNTER__ ## _impl = raii(f);
    
    SCOPE_EXIT([](){std::cout<<"asdf\n";});
    

    Und hey, das ist doch genau das, was im letzten Thread stand.

    (Code ungetestet)


Log in to reply