Generischer Resourceholder



  • Ich begegne oft dem Problem, dass ich Resourcen holen und wieder frei geben muss. Anstatt zum Beispiel am Anfang einer Funktion die Resource zu holen und bei allen returns und exceptions je nach Fall die Resource frei zu geben, was bei mehreren Resourcen kompliziert wird, kann man ja das Resource holen im Konstruktor einer Klasse und das Freigeben im Destruktor machen und dann ein Objekt erstellen. Das hat den Vorteil, dass man sich ums Freigeben nicht mehr kümmern braucht. Solche Klassen zu schreiben ist irgendwie redundant, dafür gibt es sicherlich eine allgemeine Form.

    //beispielhafte nutzung mit fantasiesyntax
    Resourceholder mrh<Mutex mutex, {mutex.aquire();}, {mutex.release();}>;
    
    Resourceholder frh<FILE *f, {f = fopen("bla", "r");}, {if (f) fclose(f);}>;
    fgets(buffer, sizeof buffer, frh.f); //zugriff auf resource
    
    Resourceholder oglrh<GLuint texture, {glGenTextures(1, &texture);}, {if (texture != -1) glDeleteTextures(1, &texture);}>;
    glBindTexture(GL_TEXTURE_2D, oglrh.texture);
    
    PAINTSTRUCT ps;
    Resourceholder dcrh<HDC hdc, {hdc = beginPaint(hwnd, &ps);}, {endPaint(hwnd, &ps);}>;
    FillRect(dcrh.hdc, &ps.rcPaint, (HBRUSH)GetStockObject(WHITE_BRUSH));
    

    Ich bin mit Templates nicht weit gekommen und weiß auch nicht wie ich Code per Template übergeben kann um die Klasse Resourceholder zu implementieren, wahrscheinlich braucht man Lambda-Expressions oder so. Gibt es dafür schon was fertiges? (std::resource_holder oder so)



  • Das Prinzip nennt sich RAII. Vielleicht willst du Smartpointer?



  • Ja unique_ptr bzw. shared_ptr jeweils mit custom deleter. Und Dein Api-Wrapper hat dann Helfer-Funktionen, die die Erzeugung des ganzen Konstrukts übernehmen (weil die ja wiederum selber so ein RAII-Wrapper sind).



  • Wie sähe denn Beispiel 2 mit dem FILE * aus?
    Smartpointer fand ich nicht zwingen, zum Beispiel läuft das erste Beispiel gut ohne Smartpointer, wenn man Daten für eine Funktion locken will, und diese dann bei Ende der Funktion automatisch unlocked werden.



  • http://www.c-plusplus.net/forum/312289
    Für mutex gibt es std::lock_guard und std::unique_lock.



  • struct Deleter {
    	void operator()( FILE* f ) { fclose(f); } 
    };
    
    std::unique_ptr< FILE, Deleter > ptr( fopen("aa",0) );
    

    Irgendwie so!



  • Es gibt auch noch den ScopeGuard (Boost und Originalartikel).

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



  • Decimad schrieb:

    Irgendwie so!

    Oder falls man den Typ gleich behalten und nicht jedes Mal einen neuen Funktor schreiben will:

    std::unique_ptr<FILE, std::function<void()>> ptr( std::fopen("xy", "r"), &std::fclose );
    

    Geht auch mit Lambdas. Hat aber kleinen Performancenachteil durch std::function .

    <💡>



  • Ahhh, genau, da war ja eine Typweiche für empty-Deleter-Klassen eingebaut. Gut, dass Du mich gerade wieder daran erinnerst!



  • glühbirne schrieb:

    Oder falls man den Typ gleich behalten und nicht jedes Mal einen neuen Funktor schreiben will:

    std::unique_ptr<FILE, std::function<void()>> ptr( std::fopen("xy", "r"), &std::fclose );
    

    Das wär ja nur von Vorteil, wenn man noch bevor die Datei geschlossen wird, unterschiedliche Aktionen durchführen wollen würde. Wenn man das nicht braucht, sollte man das mit std::function IMHO eher sein lassen.



  • 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


Anmelden zum Antworten