Generischer Resourceholder



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



  • Nexus schrieb:

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

    Ehm... Was du nicht sagst?



  • Nexus schrieb:

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

    Meep, geht nicht. http://www.gotw.ca/gotw/047.htm. Scope(failure) ist in C++ schlicht unmöglich korrekt hinzubekommen (ohne bool-Flag).



  • Kellerautomat schrieb:

    Ehm... Was du nicht sagst?

    Der Thread geht um "generische Resourceholder". Wenn du ohne Kommentare nur ein cooles "+1 fuer scope(exit/failure)" übrig hast, ist das relativ wenig hilfreich. Und in diesem Punkt muss ich cooky451 zustimmen: So wie auf Stackoverflow für scope(exit) geworben wird, wäre -- zumindest sobald man das mehrmals braucht -- ein richtiger RAII-Wrapper sinnvoller.

    alexandrescu schrieb:

    http://www.gotw.ca/gotw/047.htm

    Sagt der Artikel nicht nur, dass uncaught_exception() ungeeignet ist, um das Exception-Werfen in Destruktoren davon abhängig zu machen? Das Problem tritt ja bei verschachtelten Statements auf, wobei scope(failure) üblicherweise für lokale Variablen von Funktionen eingesetzt wird. Aber es bliebe wohl ein Hack.

    alexandrescu schrieb:

    Scope(failure) ist in C++ schlicht unmöglich korrekt hinzubekommen (ohne bool-Flag).

    Es scheint aber Implementierungen zu geben, z.B. die Bibliothek stack_unwinding. Der Entwickler sagt, er benutze ein plattformabhängiges uncaught_exception_count() , ich weiss nicht ob es mit uncaught_exception() genau gleich möglich wäre.



  • Nein, kann man nicht. uncaught_exception_count() wird aber gerade im isocpp Forum diskutiert und moeglicherweise mit C++14 standardisiert.



  • @Nexus
    Du kannst jedes beliebige C++ Programm folgendermassen transformieren:

    // Alte main() in original_main() umbenennen und dann...:
    
    struct original_main_caller
    {
        int& m_rc;
    
        original_main_caller(int& rc) : m_rc(rc) {}
        ~original_main_caller()
        {
            m_rc = original_main();
        }
    };
    
    int main()
    {
        int rc = 0;
        try
        {
            original_main_caller omc(rc);
            throw 1;
        }
        catch (int)
        {
            return rc;
        }
    }
    

    Jetzt gibt uncaught_exception immer true zurück und ist damit zu nichts mehr zu gebrauchen.

    Das Beispiel ist konstruiert, aber in ausreichen grossen Programmen finden sich früher oder später genug Stellen wo ein über uncaught_exception implementiertes on_scope_failure Mist bauen würde.

    Daraus folgt dass uncaught_exception grundsätzlich für nichts zu gebrauchen ist, es ist einfach nur ein Design-Fail.


Anmelden zum Antworten