static und Destruktur-Aufruf



  • Hallo!
    Ich möchte für mein Programm einen Resourcenpool schreiben (intern std::queue), aber davon soll der Nutzercode nichts merken. Aus gewissen Gründen ist diese queue ein statischer Member einer Template-Klasse.
    In dem Pool sind nur leider Resourcen, die ich freigeben müsste, aber erst am Ende des Programms (da, wo statischer Speicher freigegeben wird). Wie kann ich das machen?

    struct resource {
       resource() {
          cout << "Allocated" << endl;
       }
    
       ~resource() {
          cout << "Deallocated" << endl;
       }
    };
    
    std::deque allocate_resources() { ... } // RVO, daher copy return
    void deallocate_resources(std::deque&) { ... }
    
    template <typename T>
    struct type {
       static std::queue<resource> resources;
    };
    
    std::queue<resource> type::resources(allocate_resources()); // Wird noch vor main() aufgerufen
    
    int main() {
       cout << "Startup" << endl;
       type t;
       cout << "Shutdown" << endl;
    }
    

    Gewünschte Ausgabe:

    Allocated!
    Allocated!
    Allocated!
    ...
    Startup
    Shutdown
    Deallocated!
    Deallocated!
    Deallocated!
    ...
    

    Die "Deallocated"-Nachrichten kommen natürlich nie, da ja nirgends deallocate_resources() aufgerufen wird. Wie kann ich das machen? Im Usercode soll später nichts von dem Pool zu sehen sein.



  • Mach für deine type::resources halt einen Typ der im Konstruktor allocate_resources() und im Destruktor deallocate_resources() aufruft (genau das was dein struct resource da tut, wofür is das eigentlich wenn dus nirgendwo verwendest)!? Du musst dabei nur aufpassen dass die Reihenfolge in der statische Variablen initialisiert werden nicht definiert ist. Wenn du also ein anderes globales/statisches Objekt hast das von diesem Ding abhängt dann hast du ein Problem. Ansonsten funzt das aber wunderbar.



  • > Mach für deine type::resources halt einen Typ der im Konstruktor allocate_resources() und im Destruktor deallocate_resources() aufruft

    Dann müsste ich ja std::queue wrappen, und alles wie pop, push, front etc. definieren. Das ist sehr umständlich.
    Sollte ich von queue ableiten?



  • Jodocus schrieb:

    Dann müsste ich ja std::queue wrappen, und alles wie pop, push, front etc. definieren.

    So what!? Und außerdem kannst du dem Typ auch einfach eine Methode geben die ne Referenz auf die queue zurückgibt oder die Variable public machen. Abgesehen davon stellt sich die Frage ob du wirklich das allgemeine Interface einer queue haben willst (was soll das Ding denn genau machen?). Und ansonsten werden natürlich über den Destruktor der queue auch die Objekte in der Queue zerstört, also deren Destruktoren aufgerufen, vielleicht kannst du auch damit was anfangen.

    EDIT: Ich seh grad dass du letzteres eh schon in deinem Beispielcode verwendest, das ist mir oben entgangen. Wo genau liegt denn dort das Problem, die Destruktoren sollte da aufgerufen werden, wenn nicht dann ist dein Compiler grob fehlerhaft.

    Jodocus schrieb:

    Sollte ich von queue ableiten?

    Auf keinen Fall.



  • > Auf keinen Fall.

    Nur, weil der Destruktor von std::queue nicht virtuell ist? Das macht doch nix, die abgeleitete Klasse definiert ja keine neuen Member, sondern nur ein neuer Konstruktor/Destruktor. Polymorph muss der Typ ja nicht sein.



  • Die std:: Container sind nicht dazu gedacht dass man davon ableitet. Auch wenn das in deinem konkreten Fall im Moment keine wirklichen Probleme bereitet ist es trotzdem hässlich. Warum nicht einfach ordentlich machen?

    Wie gesagt: Was genau repräsentiert diese queue? Brauchst du wirklich das komplette allgemeine Interface (vermutlich nicht)? Wo genau liegt eigentlich das Problem mit deiner Lösung über dieses resource struct? Die Destruktoren müssten nämlich eigentlich aufgerufen werden, wenn nicht dann wäre das ein ziemlich grober Fehler in deinem Compiler. Oder schaut dein echter Code doch grundsätzlich anders aus (dein Beispielcode ist nämlich syntaktisch nicht ganz korrekt)? Sollte nämlich eigentlich funktionieren: http://codepad.org/AKYb0SKq



  • Ich werde wohl mal noch etwas konkreter, zumal ich jetzt immer mehr Fragen bekomme.

    > Wie gesagt: Was genau repräsentiert diese queue?

    Den Speicher, wo die Resourcen des Pools liegen. Sobald eine Resource angefordert wird, wird sie aus der Queue entfernt und einem RAII-Objekt übergeben. Sobald das zerstört wird, wird das Handle in die Queue zurückgeschrieben.
    Diese Queue befindet sich eben im statischen Speicher (als ein Klassenmember einer Template-Instanz). Das heißt also, dass zum Programmbeginn der Pool initialisiert wird (sofern irgendwann im Code das Template instantiiert wird). Nur habe ich jetzt das Problem, dass ich die Resourcen aus der Queue freigeben muss, wenn die Queue selbst zerstört wird:

    // Diese Struktur muss ganz am Anfang und ganz am Ende des Programmes in Aktion treten ---> static-Instanz
    struct library_guard {
       library_guard() {
          if(system_api::init_library() == false) {
             // Fehler aufgetreten - was tun? Exceptions sind hier nicht möglich, da nicht von außen fangbar
             // std::abort()? Dann kann der Anwender nicht sehen, was passiert ist. ?
          }
       }
    
       ~library_guard() {
          if(system_api::free_library() == false) {
             // Problem wie im Konstruktor
          }
        }
    };
    
    namespace {
       library_guard guard__; // statisches Objekt, wird es als erstes instantiiert?
    }
    
    std::deque create_handles() {
       typedef std::deque<unsigned> handle_deque; // deque deshalb, weil später mehr Handles dazukommen
       handle_deque handles(1024);                // vector eignet sich zum Hinzufügen von Elementen nicht
       for(handle_deque::size_type n = 0; n < handles.size(); ++n)
          if((handles[n] = system_api::allocate(...)) == false) {
             // Fehler aufgetreten - was tun? Exceptions sind hier nicht möglich, da nicht von außen fangbar
             // std::abort()? Dann kann der Anwender nicht sehen, was passiert ist. ?
          }
       return handles; // Wird als R-Value benutzt ---> copy return optimiert durch RVO
    }
    
    template <typename T>
    struct A {
       static std::queue<unsigned> handles; // Statisch, da von A keine Objektinstanz erstellt werden darf
    private:
       A();
    };
    
    template <typename T>
    std::queue<unsigned> A<T>::handles(create_handles()); // <---- das wird nun vor main() initialisiert, falls im Code ein Template von A<> instantiiert wird
    /*
    aber static-Instantiierungs-Reihenfolge ist unbekannt; Wie kann library_guard _davor_ instantiiert werden?
    */
    
    int main() {
       ...
    }
    

    Am Ende der Anwendung wird der von der std::queue benutze Heap-Speicher freigegeben. Aber ich muss explizit die Handles freigeben. Wie kann ich das elegant machen, ohne das std::queue-Interface nachzubauen? Und wie handhabe ich die Fehlerbehandlung und die Instantiierungs-Reihenfolge?



  • ein singleton?
    Im Konstruktor die initialisierung oder eine fuktion dafür bereit stellen. Diese dan vor main() aufrufen.
    Im Destruktor dan alles wieder frei geben.

    Zu: // Fehler aufgetreten - was tun? Exceptions sind hier nicht möglich, da nicht von außen fangbar
    Fehler-Flag setzen und dan bei begin von main() abfragen?



  • Der Clue ist, dass ich library_guard im Usercode gerne verdecken möchte. Da man den immer braucht, könnte die Library sie doch selber aufrufen, weshalb ich ein statisches Objekt vom Guard erzeuge.

    > Im Konstruktor die initialisierung oder eine fuktion dafür bereit stellen. Diese dan vor main() aufrufen.

    Wie rufe ich eine Funktion vor main() auf? Das geht AFAIK nur in Konstruktoren von globalen (statischen) Objekten.



  • Jodocus schrieb:

    Sobald eine Resource angefordert wird, wird sie aus der Queue entfernt und einem RAII-Objekt übergeben. Sobald das zerstört wird, wird das Handle in die Queue zurückgeschrieben.

    Warum auch immer du dafür eine globale Instanz brauchst...Jedenfalls braucht dieses Ding dann nach außen hin wie vermutet wohl nicht das interface einer queue sondern offenbar nur genau zwei (statische) Methoden, eine um eine Ressource anzufordern und eine weitere um sie wieder freizugeben!?

    Jodocus schrieb:

    Nur habe ich jetzt das Problem, dass ich die Resourcen aus der Queue freigeben muss, wenn die Queue selbst zerstört wird

    Warum packst du in deine queue anstatt von unsigned int nicht einfach einen Typ der sich über ref counting oder so drum kümmert dass alles freigegeben wird?

    Die Reihenfolge in der solche globalen Objekte initialisiert werden ist nur zwischen verschiedenen Übersetzungseinheiten undefiniert. Alle globalen Variablen die in der selben .cpp Datei definiert sind werden in der Reihenfolge initialisiert in der sie definiert sind.

    Trotzdem würd ich mir überlegen ob dieses Singleton-ähnliche Design wirklich so eine gute Idee ist. Wenn du mich fragst wäre es wohl besser irgendwo im Programm so einen Ressourcen Pool zu erzeugen und dann allen Objekte die ihn brauchen eine Referenz drauf zu geben. Damit lösen sich praktisch alle Probleme von selbst, auch die Probleme mit den Exceptions und vor allem natürlich die großen Probleme die durch ein Singleton erst erzeugt werden.



  • > Warum auch immer du dafür eine globale Instanz brauchst...

    Für mich ist ein Pool was globales, ein allgemeiner Speicher.

    > Jedenfalls braucht dieses Ding dann nach außen hin wie vermutet wohl nicht das interface einer queue sondern offenbar nur genau zwei (statische) Methoden, eine um eine Ressource anzufordern und eine weitere um sie wieder freizugeben!?

    Nach außen existieren auch nur genau diese 2 statischen Memberfunktionen. Die queue dient - wie gesagt - nur als interner Speicher. Welchen Container ich hier benutzte spielt zunächst auch keine Rolle, er muss eben nur korrekt alles anfordern und freigeben

    > Warum packst du in deine queue anstatt von unsigned int nicht einfach einen Typ der sich über ref counting oder so drum kümmert dass alles freigegeben wird?

    Was bringt mir eine Queue von shared_ptrs? Die Objekte sollen alle am Ende des Programms freigegeben werden (von der Library, nicht vom Usercode).

    > Trotzdem würd ich mir überlegen ob dieses Singleton-ähnliche Design wirklich so eine gute Idee ist.

    Mein Hirn ist quasi schon verknotet, da dieses Problem für mich so kniffelig ist. Ich will eben 2 Sachen gleichzeitig verdecken: den Pool (der soll nämlich auch austauschbar sein, damit am Usercode nichts geändert werden muss), der quasi wie ein künstlicher Heap-Speicher funktionieren soll und die System-Library-Instanz (eine Windows DLL-Instanz), welche für alle Operationen im Zusammenhang mit der Library benutzt wird.
    Einfache Lösung wäre eben:

    #include "meine_library.hpp"
    
    int main() {
       load_library();
       {
         resource_pool pool(100); // 100 Elemente im Pool
         resource r1 = pool.get(); // RAII-Objekt r1 bekommt Resource, die aus dem Pool gelöscht wird
       } // r1 wird zerstört, Destruktor kann interne Resource in pool zurückschreiben, Pool wird zerstört --> Resourcen an System freigegeben
       free_library();
    }
    

    Aber ich will:

    #include "meine_library.hpp"
    
    int main() {
       // System-Library automatisch laden/freigeben (vor der main() nur mit static möglich, aber keine Exceptions :(
       // Pool in der Library automatisch erstellen
       resource r1; // Konstruktor benutzt den library-internen Pool und nimmt eine Resource heraus
    } // r1 wird zerstört --> Resource wird in internen Pool zurückgeschrieben
    
    // Der interne Pool muss nun irgendwie nach der main() freigegeben werden (mit static möglich, aber leider keine Exceptions :(
    

    Wie kann ich das nur erreichen? 😞


Anmelden zum Antworten