Generischer Resourceholder



  • alexandrescu schrieb:

    In D gibt es dafür u.a. scope(exit) , vielleicht bekommt C++ so etwas auch einmal.

    Lieber nicht, C++ hat dafür schon ein besseres Konzept.



  • cooky451 schrieb:

    alexandrescu schrieb:

    In D gibt es dafür u.a. scope(exit) , vielleicht bekommt C++ so etwas auch einmal.

    Lieber nicht, C++ hat dafür schon ein besseres Konzept.

    Was schlägst du für scope(success) und scope(failure) als Alternative vor?



  • Destruktoren? RAII?



  • alexandrescu schrieb:

    cooky451 schrieb:

    alexandrescu schrieb:

    In D gibt es dafür u.a. scope(exit) , vielleicht bekommt C++ so etwas auch einmal.

    Lieber nicht, C++ hat dafür schon ein besseres Konzept.

    Was schlägst du für scope(success) und scope(failure) als Alternative vor?

    Zeig doch mal ein Beispiel, wo so etwas extrem nützlich ist und du nicht weißt, wie man das in C++ ohne scope(sowieso) elegant hinschreiben kann.



  • nwp3, mit Scope Guards kannst du sowas schreiben:

    void function()
    {
        Mutex m;
        m.acquire();
        ON_SCOPE_EXIT( m.release(); )    
    }
    

    Du kannst prinzipiell beliebigen Code an ON_SCOPE_EXIT übergeben. Das lohnt sich vor allem, wenn du oft verschiedene Arten zur Freigabe hast. Um Ressourcen wie Mutex oder FILE* mehrmals wiederzuverwenden, bietet sich hingegen ein eigener Typ als RAII-Wrapper an.

    Die Implementierung verwendet Lambda Expressions und Makros (wir wollen, dass für verschiedene Zeilen verschiedene Scope-Guard-Objekte erstellt werden):

    template <typename Fn>
    struct ScopeGuard
    {
    	ScopeGuard(Fn&& deleter)
    	: deleter(std::move(deleter))
    	{
    	}
    
    	~ScopeGuard()
    	{
    		deleter();
    	}
    
    	Fn deleter;
    };
    
    template <typename Fn>
    ScopeGuard<Fn> makeScopeGuard(Fn deleter)
    {
    	return ScopeGuard<Fn>(std::move(deleter));
    }
    
    #define PP_CAT_IMPL(a, b) a ## b
    #define PP_CAT(a, b) PP_CAT_IMPL(a, b)
    #define ON_SCOPE_EXIT(...) auto PP_CAT(_scopeguard, __LINE__) = makeScopeGuard([&] () { __VA_ARGS__ });
    


  • Nichts gegen Alexandrescu, aber ScopeGuard ist doch furchtbar unelegant.



  • cooky451 schrieb:

    Nichts gegen Alexandrescu, aber ScopeGuard ist doch furchtbar unelegant.

    Meinst du meinen Code? Der ist nicht von Alexandrescu, auch wenn er die zu Grunde liegende Idee hatte.

    Was ist daran unelegant? Störst du dich an den Makros?



  • Ich störe mich an der Grundidee Besitz und Freigabe zu entkoppeln.



  • cooky451 schrieb:

    Ich störe mich an der Grundidee Besitz und Freigabe zu entkoppeln.

    Ist ja auch nicht so.
    Im Beispiel mit dem Mutex-Lock "besitzt" der Scope-Guard den Lock.
    Bzw. wenn man nen FILE* und einen fclose() Scope-Guard hat, dann besitzt natürlich auch der Scope-Guard das File.

    Wobei Dinge wie ON_SCOPE_EXIT sowieso nur Hilfestellungen für Fälle sind, in denen es sich einfach nicht auszahlt eigene Guard-Klassen bzw. RAII Klassen zu schreiben, weil man irgendwas nur an 1 oder 2 Stellen braucht.



  • Für'n lock wär's okay, aber dafür gibt's lock_guard bzw. unique_lock*. Für ein FILE* ist es einfach nur fail. Was ist wenn man das FILE* zurückgeben will? Ist einfach nicht sauber.

    *und das aus gutem Grund http://stackoverflow.com/questions/6731027/boostunique-lock-vs-boostlock-guard



  • ScopeGuard soll nicht Smart-Pointer oder RAII-Wrapper ersetzen. Ich halte es aber für fragwürdig, alles, was mit Scopes und RAII zu tun hat, kategorisch in den unique_ptr -Deleter reinzuwürgen. Wie schon von hustbaer angetönt, ist ScopeGuard im Bezug auf Freigabe vor allem nützlich, wenn sich das Schreiben einer eigenen Klasse nicht lohnt.

    Zum anderen kann man damit recht elegant Rollback-Semantik implementieren. Entsprechende Aktionen sind meist kontext-spezifisch und nicht zwingend an ein einzelnes Ressourcen-Objekt gebunden. Eine eigene Klasse müsste die Logik vom Anwendungscode trennen und hätte nur eine einzige Instanz. Mit ON_SCOPE_EXIT drückt man hingegen direkt am Anfang des Blocks aus: Falls der Commit fehlschlägt, sieht der Rollback so aus. Man hat damit ein nützliches Tool, um Exceptionsicherheit zu realisieren.



  • Nexus schrieb:

    ScopeGuard soll nicht Smart-Pointer oder RAII-Wrapper ersetzen.

    Wird aber leider oft dafür benutzt.

    Nexus schrieb:

    Ich halte es aber für fragwürdig, alles, was mit Scopes und RAII zu tun hat, kategorisch in den unique_ptr -Deleter reinzuwürgen.

    Stimmt, wir brauchen unique_handle. 👍

    Nexus schrieb:

    Wie schon von hustbaer angetönt, ist ScopeGuard im Bezug auf Freigabe vor allem nützlich, wenn sich das Schreiben einer eigenen Klasse nicht lohnt.

    Inwiefern ist unique_ptr/unique_handle nutzen äquivalent zum Schreiben eine eigenen Klasse?

    Nexus schrieb:

    Zum anderen kann man damit recht elegant Rollback-Semantik implementieren. Entsprechende Aktionen sind meist kontext-spezifisch und nicht zwingend an ein einzelnes Ressourcen-Objekt gebunden. Eine eigene Klasse müsste die Logik vom Anwendungscode trennen und hätte nur eine einzige Instanz. Mit ON_SCOPE_EXIT drückt man hingegen direkt am Anfang des Blocks aus: Falls der Commit fehlschlägt, sieht der Rollback so aus. Man hat damit ein nützliches Tool, um Exceptionsicherheit zu realisieren.

    Dafür ist es okay. Nicht für Ressourcen.



  • cooky451 schrieb:

    Nexus schrieb:

    Zum anderen kann man damit recht elegant Rollback-Semantik implementieren. Entsprechende Aktionen sind meist kontext-spezifisch und nicht zwingend an ein einzelnes Ressourcen-Objekt gebunden. Eine eigene Klasse müsste die Logik vom Anwendungscode trennen und hätte nur eine einzige Instanz. Mit ON_SCOPE_EXIT drückt man hingegen direkt am Anfang des Blocks aus: Falls der Commit fehlschlägt, sieht der Rollback so aus. Man hat damit ein nützliches Tool, um Exceptionsicherheit zu realisieren.

    Dafür ist es okay. Nicht für Ressourcen.

    Ach Cooky, lass mal die Bibel stecken.

    Es macht einfach keinen Sinn für eine Resource deren Typ man 1x oder 2x im ganzen Programm braucht eine eigene scoped_xxx Deleter-Hilfsfunktion oder Hilfsklasse zu schreiben.
    Wenn man was fertiges hat, OK, verwenden, aber wenn man sich erst noch Code schreiben sollte ... neh, in solchen Fällen nehme ich gerne ON_SCOPE_EXIT . Und nu erzähl mir bitte nicht dass das unsauber ist.

    BTW: entwickelst du irgendwelche Projekte wo es eine Rolle spielt wie lange du dafür brauchst? Beispielsweise weil dich jmd. dafür bezahlt? Es macht nämlich sehr den Eindruck dass das nicht so wäre.



  • hustbaer schrieb:

    Wenn man was fertiges hat, OK, verwenden, aber wenn man sich erst noch Code schreiben sollte ... neh, in solchen Fällen nehme ich gerne ON_SCOPE_EXIT . Und nu erzähl mir bitte nicht dass das unsauber ist.

    Wenn du mich fragst ist das äußerst unelegant. Einfacher Grund: Ressourcenallokation und automatisch Freigabe sind im Code nicht mehr atomar. Ich kann z.B. vor den Aufruf von ON_SCOPE_EXIT() anderen Code einfügen und schon ist alles kaputt; oder was ist mit mehreren derartigen Objekten im selben Scope, wenn da ein acquire werfen kann und die Reihenfolge der ON_SCOPE_EXIT durcheinanderkommt wars das schon wieder. Mit ordentlichem RAII wäre das dagegen rein prinzipiell ausgeschlossen...

    Und abgesehen davon dass man vermutlich 99%+ aller Fälle wohl mit ein paar allgemeinen Templates wie einem unique_handle etc. erschlagen kann (unique_ptr ist leider nur für Pointer brauchbar): Auch wenn du mit der Art von Ressource nur ein oder zweimal zu tun hast, ist ein einfacher RAII Guard schnell geschrieben und wird wohl sehr wahrscheinlich irgendwann mal wieder benötigt...



  • Et tu, dot? 😞



  • Naja, ich hab mich schon öfters in der Situation gefunden, dass ich so ein RAII Teil für eine Ressource benötigt hätte, die ich nur einmal im ganzen Projekt benutze. Ich hab die 10 min. jedes Mal investiert und es bisher noch nie bereut, weil ich den Code spätestens in einem anderen Projekt weiterverwenden konnte... 😉



  • Ich denke man sollte immer abwägen wie gross der Aufwand ist wenn man etwas wiederverwendbares schreiben will, und wie hoch die Chance ist dass es auch wirklich nochmal zum Einsatz kommt.
    Und ich habe diesbezüglich schon viel schreckliches gesehen - und zwar auf beiden Seiten der goldenen Mitte.

    Ich denke immer wieder mit Schrecken an Programme wo man die selben 3/5/100 Zeilen zigfach in EINEM Programm findet, weil der Programmierer zu doof/faul/... war irgendwo eine Funktion/Klasse/... dafür zu schreiben.
    Genau so kenne ich aber auch einige Fälle wo wirklich viel Aufwand betrieben wurde um alles schön generisch und wiederverwendbar zu machen, und nichts davon wurde jemals irgendwo wieder verwendet.

    Also wenn ich irgendwo ein File schreibe, und keine Library habe die passende RAII File Klassen hat, dann schreib ich selbst eine. Die Chancen dass man sowas nochmal braucht stehen gut.

    Wenn ich im Projekt X für den Kunden Y den Labelprinter Z ansteuere, und zwar über ein SDK vom Hersteller, das nur mit genau diesem einen Drucker kompatibel ist...
    Und wenn ich dann an genau einer Stelle in dem Programm 1x den Drucker ansprechen muss...
    Und nicht damit rechne noch jemals etwas mit diesem Drucker zu machen...
    Dann werde ich vermutlich keine eigene RAII Klasse dafür schreiben.
    Weil es sich eher nicht auszahlt.



  • Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst. Ich weiß gar nicht ob's da schon was zu gibt, aber wenn nicht, werde ich das wohl mal für C++14 vorschlagen. Die Klasse muss eigentlich in den Standard.



  • +1 fuer scope(exit/failure).



  • cooky451 schrieb:

    Der Punkt ist, dass du quasi alle Fälle mit unique_handle sehr sauber erschlagen kannst.

    Könntest du kurz dessen Anwendung für die Beispiele des Threaderstellers (z.B. FILE* ) aufzeigen?

    Kellerautomat schrieb:

    +1 fuer scope(exit/failure).

    Falsche Sprache. In C++ müsstest du dafür ON_SCOPE_EXIT benutzen. Ein ON_SCOPE_FAILURE könnte man zwar mit std::uncaught_exception() hinfrickeln, aber ob sich das lohnt...


Anmelden zum Antworten