Speicherverwaltung



  • Moin,

    Wie handhabt ihr das Freigeben von Speicher? Ich möchte bspw. aus einer Methode eine Instanz eines abstrakten Typs zurückgeben. Da dies ja zwangsweise als Objekt nicht geht, muss es eine Referenz oder ein Pointer sein. Eine Referenz auf ein lokales Objekt ist jedch eher ungünstig. Demnach bin ich nahezu gezwungen einen Pointer auf einen mit new erzeugten Speicherbereich zurück zu geben. Ich bin aber der Meinung, dass der Benutzer (ist in diesem Fall eine Library bzw. ein Modul) nur den Speicher freigeben muss, der von ihm auch mit new erzeugt wurde (was ja in diesem Fall nicht zutreffen würde). Wie handhabt ihr solche Situationen? Im Moment neige ich stark zum shared_ptr. Aber demnach müsste des komplette System auf shared_ptr umstellen, da ja sonst die Referenzzählung nicht mehr passen würde. Bisher arbeitet das System hauptsächlich mit Referenzen. Hier müsste ich ja dann überall wieder die Überprüfung machen ob der Pointer nicht NULL ist usw. usw. ... Gibt es einen sauberen, allgemein benutzen Weg?

    Viele Grüße



  • Ich würde std::unique_ptr nehmen.



  • Mh gibt es keinen Weg ohne xy_ptr? Grundsätzlich find ich die auch gut, aber ich kenne keine Bibliothek bei der ich aus irgendeiner Funktion/Methode einen xy_ptr zurück bekomme ... immer Objekte oder Referenzen. Deshalb vermute ich dass hier mit meinen xy_ptr ein wenig auf dem falschen Weg bin. Rein zur Sauberkeit würde ich für dieses Modul (das ja auch nicht als Endprogramm fungiert) auf diese xy_ptr verzichten, wenn es geht.



  • skNiNe schrieb:

    Grundsätzlich find ich die auch gut, aber ich kenne keine Bibliothek bei der ich aus irgendeiner Funktion/Methode einen xy_ptr zurück bekomme ...

    Dann kennst du wahrscheinlich vor allem ältere C++-Bibliotheken, bei denen sich der Benutzer noch um die Speicherverwaltung kümmern muss. Besonders viel halte ich von solchen Bibliotheken nicht.

    skNiNe schrieb:

    Rein zur Sauberkeit würde ich für dieses Modul (das ja auch nicht als Endprogramm fungiert) auf diese xy_ptr verzichten, wenn es geht.

    Wie sieht es überhaupt mit den Besitzverhältnissen aus? Davon hängt nämlich ab, was für eine Art Verweis du nimmst. Gibt die Methode nur einen Zeiger auf ein intern genutztes Objekt zurück? Dann reicht T* oder T& . Bekommt der Aufrufer die Verantwortung über das Objekt? Dann auto_ptr<T> oder mit C++0x unique_ptr<T> . Wird der Besitz zwischen mehreren Benutzern geteilt und der letzte gibt frei? Das wäre ein Fall für shared_ptr<T> .



  • Natürlich gibt es keine Bibliothek mit unique_ptr. Der wird erst im nächsten C++-Standard eingeführt, der noch nichmal ganz verabschiedet ist. In der Zukunft wird es solche Bibliotheken sicherleich geben. Generell ist es aber genau das, was du suchst: du zeigst damit explizit, dass der Besitz an den Aufrufer übergeht, während dieser gleichzeitig keine explizite Freigabe tätigen muss. shared_ptr ist bei dir nur unnötiger Overhead, der Besitz wird ja nicht geteilt, sondern übergeben. Wenn du unique_ptr nicht willst, geht auch auto_ptr, der hat aber ein paar Nachteile im Vergleich zu unique_ptr.



  • Erstmal vielen Dank für hilfreichen Antworten bisher :).

    Ich bin erst nochmal kurz weg, anschließend kläre ich mal die Situation. In dem genannten Beispiel ist es tatsächlich so, dass der Aufruf den Besitz über den Pointer übernehmen soll/muss. Im kompletten Modul schaut das ganze etwas anders aus. Für verschiedene Typen hab ich eine zentrale Stelle zur Speicherung, bspw. eine list. nach außhin hin werden entsprechend nur Referenzen gegeben.



  • Ich denke es wäre jetzt zu langwierig die komplette Situation zu erklären. Es sollte sich aber unter Verwendung der smart-pointern leicht umsetzten lassen. Kann mir evtl. jemand noch ein paar Bibliotheken nennen, die dieses Prinzip verwenden?

    Viele Grüße



  • Findet ihr es sinnvoll, ein Programm komplett mit Templates aufzubauen? Da die meisten Fälle bei mir im Programm so ausschauen, dass die Polymorphie zur Laufzeit nicht nötig ist, wäre es normalerweise ein logischer Schritt einen Ansatz mit Templates zu fahren. Was haltet ihr davon? Somit hätte ich auch keine Probleme mit abstrakten Typen und Pointern usw. ... da ja direkt eine Instanz der richtigen Klasse zurückgegeben werden kann. Gibt es eine Möglichkeit ewig lange Template-Paramter-Listen zu vermeiden? Wenn eine Templateklasse eine Referenz auf eine andere Templateklasse hält bzw. irgendwas damit zutun hat, muss die referenzierende Klasse ja die Template-Parameter der referenzierten Klasse ebenfalls enthalten (ausser die können hier schon definiert werden bzw. durch einen richtigen Typ ersetzt werden). Diese Tatsache zieht sich meistens (bei mir zumindest ^^) durch das halbe Programm durch. Irgendwann möchte ein Klassentemplate verschiedene Template-Parameter, die mit der Klasse fast komplett garnichts mehr zutun haben.



  • Zu viele Templates finde ich nicht gut, da das stark auf die Compilezeit-Abhängigkeiten schlägt. Du kannst kein Pimpl mehr anwenden und musst die Funktionsdefinitionen immer neu kompilieren. Ausserdem kann der erzeugte Code bei vielen leicht von einander abweichenden Instanziierungen unnötig gross werden.

    Aber auch abgesehen von diesen Kriterien gibt es oft Situationen, wo ich lieber Laufzeitpolymorphie verwende. Kommt auch immer etwas drauf an, wie statisch bzw. wie gekoppelt die einzelnen Komponenten sind. Bei Templates und statischer Polymorphie müssen die Klassen nichts miteinander zu tun haben, bei dynamischer Polymorphie brauchst du eine Vererbungshierarchie. Manchmal ist auch eine Kombination (Type Erasure) sehr nützlich. Jedenfalls transportiere ich polymorphe Objekte nahezu immer in Smart-Pointern. Ich hab mir sogar Smart-Pointer geschrieben, die eine polymorphe Deep-Copy durchführen können.



  • Das klingt sehr gut. Magst du mir mal deinen smart-pointer zukommen lassen ^^?

    Im Allgemeinen ist es, meiner Meinung nach, nicht immer leicht genau zu unterscheiden, wie viel Polymorphismus überahupt benöitigt wird.

    Beispiel:
    Ich hab ein Document (Wrapper um eine Datei im File-System). Dies stellt erstmal eine Schnittstelle dar. Von der aus kann ich über eine Position (line + column) auf die verschiedenen Elemente des Documents drauf zugreifen. Zudem kann ich mir einen Scanner (Iterator) geben lassen.

    Schon allein bei diesem Beispiel gibt es viele Fragen zu beantworten.

    - Soll Position ein Template sein? Evtl kann man somit den Typ von line und column angeben.
    - Mach ich den Scanner als abstrakte Klasse? Somit müsste ich aus Document bspw. einen auto_ptr auf Scanner zurück geben, sobald ich eben einen anforder.
    - Wenn ich Scanner als abstrakte Klasse mache, wäre auch eine Überlegung, ob ich Document als Template mach. Evtl könnte ich mir die Rückgabe eines auto_ptr's sparen und direkt den richtigen Typ zurückgeben.
    - Evtl. könnte ich im Document noch den char-Typen angeben (char, wchar_t) ... Somit müsste dann auch Scanner wieder diesen Typ noch mit aufnehmen, da es in der Regel eine Referenz auf Scanner halten müsste um die einzelnen Elemente im Document abzufragen.
    - Evtl. zerlege ich die Schnittstelle Document noch in zwei Teile: Read- und Writable (noch flexibler).

    Sicherlich könnte man einen einfachen und dennoch relativ sauberen Weg nehmen und schnell ans Ziel kommen. Wenn man sich aber Gedanken drüber macht, wie flexibel das ganze sein muss, welche Ansätze ich grundsätzlich benutze. Dann kann man an der kleinen Sache schon relativ lange sitzen ...



  • skNiNe schrieb:

    Das klingt sehr gut. Magst du mir mal deinen smart-pointer zukommen lassen ^^?

    Im Allgemeinen ist es, meiner Meinung nach, nicht immer leicht genau zu unterscheiden, wie viel Polymorphismus überahupt benöitigt wird.

    Beispiel:
    Ich hab ein Document (Wrapper um eine Datei im File-System). Dies stellt erstmal eine Schnittstelle dar. Von der aus kann ich über eine Position (line + column) auf die verschiedenen Elemente des Documents drauf zugreifen. Zudem kann ich mir einen Scanner (Iterator) geben lassen.

    Schon allein bei diesem Beispiel gibt es viele Fragen zu beantworten.

    - Soll Position ein Template sein? Evtl kann man somit den Typ von line und column angeben.
    - Mach ich den Scanner als abstrakte Klasse? Somit müsste ich aus Document bspw. einen auto_ptr auf Scanner zurück geben, sobald ich eben einen anforder.
    - Wenn ich Scanner als abstrakte Klasse mache, wäre auch eine Überlegung, ob ich Document als Template mach. Evtl könnte ich mir die Rückgabe eines auto_ptr's sparen und direkt den richtigen Typ zurückgeben.
    - Evtl. könnte ich im Document noch den char-Typen angeben (char, wchar_t) ... Somit müsste dann auch Scanner wieder diesen Typ noch mit aufnehmen, da es in der Regel eine Referenz auf Scanner halten müsste um die einzelnen Elemente im Document abzufragen.
    - Evtl. zerlege ich die Schnittstelle Document noch in zwei Teile: Read- und Writable (noch flexibler).

    Sicherlich könnte man einen einfachen und dennoch relativ sauberen Weg nehmen und schnell ans Ziel kommen. Wenn man sich aber Gedanken drüber macht, wie flexibel das ganze sein muss, welche Ansätze ich grundsätzlich benutze. Dann kann man an der kleinen Sache schon relativ lange sitzen ...

    Klingt nach zu viel Abstraktion als Folge von Entscheidungsschwäche. Das ist mal was Neues.



  • Mh gut möglich :/. Wie würdest du die genannte Situation umsetzen?



  • skNiNe schrieb:

    Mh gut möglich :/. Wie würdest du die genannte Situation umsetzen?

    Ich plane vielleicht weniger, sondern tippe einfach drauf los. Ich versuche schon dafür zu sorgen, daß Erweiterungen später möglich sind und nicht wie nachträglich drangeschraubt aussehen. Aber ich schreibe auch nur, was benötigt wird.

    - Soll Position ein Template sein? Evtl kann man somit den Typ von line und column angeben.
    Wozu? int32_t reicht doch.

    - Mach ich den Scanner als abstrakte Klasse?
    Wozu? Klingt langsam ohne weiteren Vorteil.

    - Wenn ich Scanner als abstrakte Klasse mache, wäre auch eine Überlegung, ob ich Document als Template mach.
    Wozu?

    - Evtl. könnte ich im Document noch den char-Typen angeben (char, wchar_t) ... Wozu? Entscheidi Dich für utf8 oder sowas und gut ist's.

    - Evtl. zerlege ich die Schnittstelle Document noch in zwei Teile: Read- und Writable (noch flexibler).
    Wozu?



  • Mh ja wäre evtl ne Idee ^^.

    Hier mal der Hintergrund des ganzen:

    Irgendwo hier im Forum fliegt ein Thread "C++ parsen" von mir rum. Im Moment probiere ich mich an einem C-Parser. Bisher bin ich einige konzeptionelle Sachen durchgegangen und mittlerweile fang ich des tippeln an ^^.

    Bei C/C++ rumpelt der Präprozessor über den Code und bearbeitet ihn bevor die tatsächliche Analyse stattfindet. Ich möchte jedoch nicht alle Änderungen in eine separate Datei schreiben und die vom C-Analyzer einlesen sondern möchte alle Änderungen intern auflösen. Damit soll folgendes verhindert werden:

    - Erneutes Laden der Datei
    - Wenn eine Datei von mehreren Dateien eingebunden werden muss, muss diese nicht immer neu analysiert und bearbeitet werden sondern existiert schon in bearbeiteter Form im Programm.

    Also angenommen ich hab eine Datei (Document) in der folgendes drin steht:

    ??=include "header.h"
    

    wird daraus nach dem ersten Durchlauf des Präprozessors ein

    #include "header.h"
    

    und im zweiten wird die Stelle durch den Inhalt von header.h ersetzt.

    Also kann ich im Endeffekt an Hand bestimmter Aktionen (löschen, ersetzen, hinzufügen, und in Datei springen) die vom Präprozessor bearbeitete Datei wie folgt simulieren (von unten angefangen):

    `in Datei header.h springen


    (ersetzen)


    ??=include "header.h"`

    Also ich kann für bestimmte Position-Ranges Aktionen hinterlegen, und je nach Durchlauf der Translation-Phase soll der Scanner eben diese Aktionen berücksichtigen (an Hand eines Mappings).

    Aus diesem Grund hab ich über einen abstrakten Scanner nachgedacht. Einen der ein Document pur liest, und einer der bspw. die Aktionen berücksichtigt. Wenn ich den Scanner als template deklarieren würde könnte ich sogar noch den Tokenizer drauf setzen (dass er mir kein char sondern ein Token liefert). Tokenizer liest aus ActionScanner und der wiederum aus dem normalen Scanner.

    Also ist meine Idee.



  • #ifdef __WIN32__
        #ifndef NDEBUG
            #define NO_MIN_MAX //ändert Verhalten von <windows.h>!
        #endif
        #include <windows.h>
    #else
        #ifdef __linux__
            #include <usistd.h>
        #else
            #define TEST zeile1\
            zeile2
        #endif
    #endif
    

    macht mir jetzt ein wenig Angst.

    Warum nicht einfach verschiedene Prozessoren bauen, die jeweils einen Zeichenstrom lesen und einen schreiben? Als Strom kannste dann eine Pipe nehmen und mußt nichts in teuren Dateien zwischenspeichern. Präcompilat zwischenspeichern darfste eigentlich nur, wenn alle Makrodefinitionen und alle Deklarationen vorher gleich waren.



  • Also mit meinem genannten Beispiel bin ich hauptsächlich mal auf das Handling von Dateien bzgl. des Scanners eingegangen. Sicher sitzt intern dahinter noch ein Präprozessor der die Grammatik aus dem Standard implementiert und dort eben Symbole definiert, wieder rauswirft usw. ... Dementsprechend werden an bestimmten Stellen auch Aktionen zum springen in Dateien hinterlegt (so nach dem erklärten Prinzip).

    Wenn du mir das mit den verschiedenen Prozessoren, lesen und schreiben usw. nochmal genauer erklären könntest wäre ich dir sehr dankbar :). Klingt schon mal gut, aber kann ich im Moment nicht genau nachvollziehen.

    EDIT:
    Jetzt weiss ich warums dir Angst macht ^^. Im Endeffekt wäre es dann so, dass ich zwar das Dokument nur einmal laden bräuchte, aber die Aktionen die ich hinterlege müssen für einen kompletten Translation-Unit gelten und nicht für die Datei selbst. Schon allein aus der Tatsache dass ich eine einzelne Datei garnicht verarbeiten kann.



  • Hintereinanderhängen im Prinzip wie

    dir c:\windows | sort /+20 | more
    

    Und benutzt wird wohl http://msdn.microsoft.com/en-us/library/aa365781(v=vs.85).aspx
    bzw daraus streambuf machen und einen istream und ostream dranbasteln. Falls man nicht alles selber zeichenweise lesen will (ich würde selber lesen wollen).

    ifstream eingabe("c:\test.cpp");
    ofstream ausgabe("c:\test.obj");
    pipe p1;
    pipe p2;
    
    compilefunc(p2.istream,ausgabe);//diese drei aber als Threads
    preprocessorfunc(p1.istream,p2.ostream);//absetzen und quasi gleichzeitig 
    bitrigraphfunc(eingabe,p1.ostream);//laufen lassen. Die Pipes 
    //synchronisieren den Ablauf schon.
    


  • Nach dem Prinzip verlier ich aber leider die Positionszuordnung. Wollte mir irgendwann wenn das Ding steht mal nen kleinen Editor zusammenbauen um mit dem Parser interaktiv Code aus dem Editor zu analysieren. Demnach müsste ich nach jeder Änderung den kompletten Code neu analyisieren.



  • skNiNe schrieb:

    Demnach müsste ich nach jeder Änderung den kompletten Code neu analyisieren.

    Das wird wohl leider oft genug vorkommen. Wenigstens, daß Du den kompletten Code hinter der Änderung neu analysieren mußt. Quellcodezeilennummern kann man sich in dem selber erfundenen Streamformat natürlich mitschicken.



  • Theoretisch müsste es auch ohne kompletter erneuten Analyse funktionieren. Schließlich hab ich am Ende nach der syntaktischen Prüfung einen AST. Darin sind alle Produktionen und letztendlich auch alle Tokens. Zu den Tokens gehört eine Referenz auf eine Position. An zentraler Stelle werden die Positionen gespeichert und aktualisiert wenn der Benutzer bspw. whitespace einfügt usw. ... Wenn ich nun in einer Funktion einen Ausdruck bearbeite, muss nur des Scope in dem der Ausdruck ist (in der Funktion) neu analysiert werden. Erst wenn die Funktion auch ungültig wird müssen alle Stellen im AST die auf die Funktion referenzieren (in welcher Form auch immer) neu analysiert werden ...

    Hab ich irgendwas nicht berücksichtigt was deiner Meinung nach dazu führen könnte, dass ich alles neu analysieren muss?

    Klingt nach nen harten Broken programmierarbeit. Aber im Moment bin ich ja eh noch mit den Sachen ganz am Anfang beschäftigt.


Anmelden zum Antworten