Garbage Collection



  • 314159265358979 schrieb:

    HeXor schrieb:

    Der Pointer soll eben dann noch in vielen anderen Funktionen benutzt werden können.

    Mit diesem Satz fange ich nichts an.

    ^^ Du hast aber schon was damit angefangen - darauf geantwortet 😛



  • Es gibt einfach zu wenig brauchbare Informationen. Sorry, aber so kann man dir leider nicht helfen.

    Von dem was ich bislang aber erahnen kann vermute ich, dass dein jetziges Problem eine unselige Aneinanderreihung von vielen anderen Dingen ist, die man in C++ eben anders als in Java löst. Ich glaube nicht, dass sich da noch viel retten lässt.



  • otze schrieb:

    Von dem was ich bislang aber erahnen kann vermute ich, dass dein jetziges Problem eine unselige Aneinanderreihung von vielen anderen Dingen ist, die man in C++ eben anders als in Java löst. Ich glaube nicht, dass sich da noch viel retten lässt.

    Das ist, glaube ich, die wichtigste Aussage, die bisher in diesem Thread gefallen ist. Dem kann ich mich nur anschließen.



  • 314159265358979 schrieb:

    otze schrieb:

    Von dem was ich bislang aber erahnen kann vermute ich, dass dein jetziges Problem eine unselige Aneinanderreihung von vielen anderen Dingen ist, die man in C++ eben anders als in Java löst. Ich glaube nicht, dass sich da noch viel retten lässt.

    Das ist, glaube ich, die wichtigste Aussage, die bisher in diesem Thread gefallen ist. Dem kann ich mich nur anschließen.

    Wer mir nicht helfen will muss es ja auch nicht tun, aber dann auch nicht solche Kommentare ablassen...

    Ich kann euch gerne meine ca. 25 files mit ca. 200-2000 Zeilen Code pro file schicken, wenn ihr dann genug Informationen habt.

    Ich denke ich habe es mittlerweile mehr als deutlich gemacht.

    Eine Klasse MyClass mit eine Member Variable _cluster, welche ich in einer Funktion foo() der Klasse MyClass initialisieren muss, damit sie auch in anderen Funktionen bla(), muh(), bar() der Klasse MyClass zur Verfügung steht. Eine definition beim definieren des MyClass objekts ist aus den beschriebenen Gründen nicht möglich, da erst innerhalb der MyClass Klasse einige Variablen dafür berechnet/definiert werden müssen. Um pointer zu vermeiden soll eine direkt Definition her, welche hier aber nicht mit _cluster = ??? realisierbar ist, da das zu definierende Objekt von einem importierten Tool deklariert wird, welches ich nicht verändern kann und welches offensichtlich den "=" Operator überlädt, verwendet werden. Daher brauche ich einen anderen Weg um dieses (ja möglichst ohne Pointer) zu realisieren, welche ich durch dem temporären Weg (der übrigens funktioniert, aber nicht schön aussieht) realisiert habe.

    Ich danke allen die mir versuchen zu helfen, aber kommentare wie "dir ist nicht zu helfen" helfen eben nicht. Und scheinbar sehen andere hier keinen hoffnungslosen Fall sondern äußern gute Vorschläge. Es ist schade wenn man in einem neuen Board eine Hoffnung sieht und sich anmeldet und dann im ersten Thread solche Kommentare bekommnt, obwohl man VERSUCHT ein Minimalbeispiel zu basteln und Informatioen zu geben ohne 20.000 Zeilen code zu posten, wie es viele ander Anfänger selbstverständlich tun. Danke.


  • Mod

    HeXor schrieb:

    Ich danke allen die mir versuchen zu helfen, aber kommentare wie "dir ist nicht zu helfen" helfen eben nicht. Und scheinbar sehen andere hier keinen hoffnungslosen Fall sondern äußern gute Vorschläge. Es ist schade wenn man in einem neuen Board eine Hoffnung sieht und sich anmeldet und dann im ersten Thread solche Kommentare bekommnt, obwohl man VERSUCHT ein Minimalbeispiel zu basteln und Informatioen zu geben ohne 20.000 Zeilen code zu posten, wie es viele ander Anfänger selbstverständlich tun. Danke.

    Das ist kein Bashen auf den Anfänger, sondern die nüchterne Feststellung, dass dir bei der Art wie du die Fragen stellst, keine nützliche Antwort zu geben ist, außer so Allgemeinfloskeln wie "C++ ist nicht Java" oder "Das ist Scheiße, wie du das jetzt machst". Nützen dir diese Aussagen etwas? Wenn nicht, dann musst du beantwortbare Fragen stellen. Das ist keine böse Absicht, dass dir niemand gute Antworten gibt, es liegt an dir. Und viel schlimmer ist, dass du das nicht einsiehst und dich gleich persönlich angegriffen fühlst, wenn man dich nach drei Seiten nutzloser Spekulation vermehrt auf diesen Umstand hinweist.



  • HeXor schrieb:

    da das zu definierende Objekt von einem importierten Tool deklariert wird, welches ich nicht verändern kann und welches offensichtlich den "=" Operator überlädt, verwendet werden.

    Der operator= wird nicht von einem Objekt überladen - das wird in einer Klasse erledigt.
    Und wenn du diese Klasse nicht verändern kannst (da "externes Tool", wie du sagst) ist dir leider auch nicht zu helfen. Entweder du schaffst es, dass das Problem im nächsten Release des Tools beseitigt wird, oder du lebst mit dem Workaround mit dem temporären Objekt.



  • Ich hab mal ein bisschen nachgeforscht. Handelt es sich um diese Bibliothek?

    Ich kann darin zumindest in der neuesten Version keine solche Überladung finden. Ich vermute, dass einer der Member von ClusterSequence keinen assignment-Operator hat, der ein konstantes Quellobjekt annimmt (im Zweifel fastjet::SharedPtr); dann kann der Compiler für ClusterSequence keinen operator=(ClusterSequence const &) generieren.

    Ich weiß aus dem Stand allerdings jetzt nicht, ob der Compiler in solchen Fällen einen operator=(ClusterSequence&) generieren sollte. Könnte eine gcc-Erweiterung sein.

    Am besten wäre vom Design her natürlich, wenn du keine halbfertigen FastJetTool-Objekte herumreichen müsstest (dann bestünde auch dieses ganze Problem nicht), aber wenn dir dieser Weg nicht offen steht, wäre ein shared_ptrfastjet::ClusterSequence hier m.E. vertretbar. Lokalität scheint hier eh nicht gewahrt zu sein, also meh in Bezug auf cache-misses. Was du allerdings nicht machen solltest, ist einen nackten Zeiger in das Objekt zu stecken.



  • seldon schrieb:

    Was du allerdings nicht machen solltest, ist einen nackten Zeiger in das Objekt zu stecken.

    Also ist meine temporäre Variante nicht vertretbar mit:

    fastjet::ClusterSequence tmp(param1,param2);
    _cluster = tmp;
    


  • So, jetzt hab ich das nochmal im Standard nachgeschlagen. Also, der gcc verhält sich da korrekt; wenn ein X &X::operator=(X const &) nicht generiert werden kann, wird (sofern möglich) ein X &X::operator=(X&) generiert. Das wird hier passiert sein.

    Vom C++-Standard her ist die temporäre Variante also möglich. Ob sie hier funktioniert, hängt davon ab, ob die Entwickler der ClusterSequence-Klasse sich Gedanken um Kopiersemantik gemacht haben. Die Verwendung eines SharedPtr deutet ein bisschen darauf hin (ohne Kopien wäre der shared-Teil überflüssig), aber ich werde mich hüten, kategorische Aussagen über Bibliotheken zu machen, mit denen ich mich überhaupt nicht auskenne. Werden in der Dokumentation der Bibliothek manchmal ClusterSequence-Objekte kopiert? Falls ja, ist deine Variante unbedenklich und im Zweifel auch besser, als eine zusätzliche Indirektion einzuführen.



  • Was mir gerade gekommen ist, ist die Idee das ganze mit dem Destruktor der Klasse zu lösen.

    Also:

    Header file *.hpp

    class MyClass {
    
    public:
      //Konstruktor und Destruktor
      MyClass();
      ~MyClass();
    
      //Funktionen
      void foo();
      void bar();
    
      //Member variable
      fastjet::ClusterSequence *_cluster;
    }
    

    Code file *.cpp

    MyClass() {}
    
      ~MyClass() {
         delete _cluster;
      }
    
      void MyClass::foo() {
        _cluster = new ClusterSequence(param1, param2);
      }
    
      void MyClass::bar() {
        [..processing..]
      }
    

    Main function

    int main(int argc, char *argv[]) {
      MyClass obj;
      obj.foo();
      obj.bar();
     return 1;
    }
    

    In diesem Fall also doch wieder einen Pointer verwenden, diesen aber aufzuräumen sobald das Klassen Object zerstört wird, also diesen im Klassen Destruktor löschen.
    Zwar bedeutet das wieder manuelle GarbageCollection, stellt aber doch sicherlich eine in sich konsistente Methode dar, welche zusätzlich die unschöne temporäre Methode umgeht!?



  • HeXor schrieb:

    Main function

    int main(int argc, char *argv[]) {
      MyClass obj;
      obj.foo();
      obj.bar();
     return 1;
    }
    

    Mach mal:

    int main(int argc, char *argv[]) {
      MyClass obj;
      MyClass obj2(obj);
    }
    

    Tipp: Du solltest den Kopier- und Zuweisungsoperator implementieren oder unschädlich machen 😉 .



  • HeXor schrieb:

    Was mir gerade gekommen ist, ist die Idee das ganze mit dem Destruktor der Klasse zu lösen.

    Gratuliere. So steht das in jedem Anfängerbuch genau an der Stelle, an der zum ersten Mal die beiden Begriffe Destruktor und manuelle Speicherverwaltung bekannt sind.

    Du kannst das wie Generationen vor dir so machen, das zwar fehleranfällig, aber definitiv besser als gar nichts zu machen. Was die anderen hier dir sagen wollen ist, dass man in der modernen C++-Programmierung nicht mehr in jedem Destruktor allerlei Resourcen freigibt, sondern die Resourcenverwaltung an spezialisierte Klassen delegiert. In dem Fall Smart-Pointer (shared_ptr etc.)

    Zwar bedeutet das wieder manuelle GarbageCollection

    Das ist, wenn du den Müll rausbringst. In der Programmierung ist Garbage Collection ein anderes Wort für automatische Speicherverwaltung.



  • HeXor schrieb:

    Was mir gerade gekommen ist, ist die Idee das ganze mit dem Destruktor der Klasse zu lösen.

    Das wurde dir auf Seite 2 Mitte und Seite 3 erster Post bereits gesagt. Aber schön, dass du jetzt endlich anfängst, C++ zu programmieren. Nun musst du nur noch einen Schritt weiter gehen und sagen: "das kann ich automatisieren" und schon bist du bei smart pointern. oder vektoren wenn du Gruppen von Objekten brauchst. Und dann wirst du nie wieder delete selbst schreiben müssen.



  • Vielleicht hilft es, den gedanklichen Vergleich zu modernem Java zu schlagen:

    Mit Java 7 wurde das Interface AutoCloseable eingeführt, das eine Möglichkeit bietet, Aufräumarbeiten (wenn auch auf sybtaktisch etwas holprige Weise) zu automatisieren, etwa Dateien oder Sockets zu schließen:

    try (
      FileInputStream file = new FileInputStream(filename);
    ) {
      // ...
    
    } // file wird automatisch geschlossen (file.close() aufgerufen)
    

    Dieser Mechanismus kommt ursprünglich von C++ und ist da für alle Ressourcenverwaltung üblich, explizit inklusive Speicher. Man braucht kein spezielles Interface und keine spezielle Aufrufssyntax, wo ein Destuktor ist, wird dieser am Ende des Blocks ausgeführt. Das C++-Äquivalent des Java-Beispiels oben wäre

    {
      std::ifstream file(filename);
    
      // ...
    
    } // file wird automatisch geschlossen (file.~ifstream() aufgerufen)
    

    Und mit Speicher macht man das halt auf die gleiche Weise in verwaltenden Klassen:

    {
      std::vector<int> v(100);
    
      // ...
    
    } // v wird hier zerstört, der Speicher sofort (!) freigegeben
    

    oder auch

    {
      std::unique_ptr<some_class> ptr(new some_class(foo, bar));
    
      // ...
    } // ptr wird hier zerstört, sein Destruktor zerstört in der Folge das Objekt, auf das er zeigt.
    

    Soviel zur grundlegenden Idee. Jetzt für Fortgeschrittene: Es ist aus ein paar Gründen sinnvoll, die Speicherverwaltungsaufgaben möglichst eng zu kapseln, also für jede Ressource ein eigenes Verwaltungsobjekt zu haben. Der Grund dafür ist zum einen, dass es übersichtlicher ist, aber auch, dass man unnötige Kopfschmerzen in Eckfällen vermeidet.

    Zum Beispiel sieht Folgendes für das ungeübte Auge völlig in Ordnung aus:

    class some_class {
    public:
      some_class()
        : p(new some_other_class(1)),
          q(new some_other_class(2)) 
      { }
    
      ~some_class() {
        delete p;
        delete q;
      }
    
    private:
      // Kopiersemantik abschalten.
      some_class(some_class const &);
      some_class &operator=(some_class const &);
    
      some_other_class *p, *q;
    };
    

    Aber was, wenn some_other_class jetzt beispielsweise so aussieht:

    struct some_other_class {
      some_other_class(int n) {
        if(n == 2) throw n;
      }
    };
    

    Rückblick:

    some_class()
        : p(new some_other_class(1)),
          q(new some_other_class(2)) // <-- das wirft jetzt eine Exception!
      { }
    

    Das some_class-Objekt ist nicht fertig konstruiert, der Destruktor kann also nicht bemüht werden; das ginge auch schief, weil q ins Nirvana zeigt. Zwar werden bereits konstruierte Teilobjekte in so einem Fall vor Verlassen des Konstruktors zerstört, aber nackte Zeiger haben keine Aufräumautomatik. Schon haben wir ein Speicherleck, und das ist gar nicht mal so einfach sinnvoll zu stopfen. Etwas der Form

    some_class()
      try
        : p(new some_other_class(1)),
          q(new some_other_class(2))
      { }
      catch(int n) {
        if(n == 2) delete p;
      }
    

    scheint möglich, aber will man sich das wirklich antun? Und wenn some_class mal weniger vorhersehbar Exceptions wirft, kriegt man noch ganz andere Probleme; so einfach ist nämlich nicht festzustellen, welche Zeiger schon gültige Werte haben.

    Vergleiche dagegen das Szenario mit ressourcenverwaltenden Klassen:

    class some_class {
    public:
      some_class()
        : p(new some_other_class(1)),
          q(new some_other_class(2)) 
      { }
    
    private:
      // Kopiersemantik abschalten.
      some_class(some_class const &);
      some_class &operator=(some_class const &);
    
      std::unique_ptr<some_other_class> p;
      std::unique_ptr<some_other_class> q;
    };
    

    Hier kann some_other_class::some_other_class Exceptions werfen, wie es lustig ist, der Konstruktor zerstört ggf. die bereits konstruierten Teilobjekte wieder, und diese räumen den angeforderten Kram wieder weg. Merke auch: Ein eigener Destruktor ist hier gar nicht mehr notwendig, weil der generierte alle notwendigen Aufgaben übernimmt.



  • Das neue Resource-try ist doch nur noch eine weitere Verschiebung des Problems. Die sollen mal eine ordentliche, GC-gebundene Lösung bringen.



  • Die gibt es in der JVM schon - wenn der GC ein Objekt aufräumt, wird die spezielle finalize-Methode ausgeführt. Nur bringt der Garbage-Kollektor einen bei den meisten Ressourcen nicht wirklich weiter - wenn ein Socket aus dem Scope geht, will man in aller Regel, dass die Verbindung gleich geschlossen wird und nicht erst, wenn die VM nicht mehr genug Heap-Speicher hat und den GC anschmeißt.

    Auch, wenn man die Kriterien für die Collection anders setzt - bei einem GC werden die Aufräumarbeiten generell so lange wie möglich verzögert, weil er anders nicht performant zu betreiben ist. Immerhin hat der Garbage-Collector Data-Races mit allen anderen Threads, und nach derzeitigem Stand der Forschung bedeutet das, dass jeder GC-Lauf ein Stop-the-world-Event beinhaltet. Stell dir ein Client-Server-System vor, in dem beide Seiten ihre Sockets so lang wie möglich am Leben halten, nachdem die Verbindung nicht mehr gebraucht wird.



  • Es gibt aber keinerlei Garantie, dass diese Finalizer jemals aufgerufen werden. Es existiert in Java kein zuverlässiger Mechanismus, um Resourcen garantiert freizugeben. Das Auto-Closeable Dingens ist nur eine weitere Krücke für das sinkende Schiff.

    Und wenn der GC so ein großes Problem "Stop-the-world-Event" ist, dann ist Java nicht die Sprache der Wahl für solch ein Client-Server System.



  • Da spricht der Experte.



  • 314159265358979 schrieb:

    Das neue Resource-try ist doch nur noch eine weitere Verschiebung des Problems. Die sollen mal eine ordentliche, GC-gebundene Lösung bringen.

    314159265358979 schrieb:

    Und wenn der GC so ein großes Problem "Stop-the-world-Event" ist, dann ist Java nicht die Sprache der Wahl für solch ein Client-Server System.

    Das wird die Tomcat-Leute aber überraschen. Man muss, um Java-Programme als Server zu betreiben, ein bisschen mit den JVM-Parametern aufpassen, aber benutzen kann man sie dafür durchaus. Das hängt auch damit zusammen, dass keine "ordentliche, GC-gebundene Lösung" für alle Ressourcentypen verwendet wird.

    314159265358979 schrieb:

    Es gibt aber keinerlei Garantie, dass diese Finalizer jemals aufgerufen werden. Es existiert in Java kein zuverlässiger Mechanismus, um Resourcen garantiert freizugeben. Das Auto-Closeable Dingens ist nur eine weitere Krücke für das sinkende Schiff.

    So ziemlich alle ressourcenverwaltenden Klassen in Java bieten Mechanismen, mit denen man diese wieder freigeben kann; in der Tat würde ich alle, die das nicht tun, als fehlerhaft bezeichnen wollen. Damit existiert ein zuverlässiger Mechanismus, Ressourcen garantiert freizugeben - nur halt vor Java 7 kein komfortabler, und auch in Java 7 keiner, der nachlässige oder schlecht informierte Programmierer davor schützt, Ressourcen zu leaken. Das kann man aber aufgrund (beispielsweise) der Existenz nackter Zeiger auch von C++ nicht behaupten.

    Es ist richtig, dass Java in Abwesenheit automatischer Variablen böse Probleme mit der Fehlerbehandlung hatte - es ist nicht untertrieben, Exceptions in diesem Zusammenhang als kaum benutzbar zu bezeichnen - aber die AutoCloseable-Lösung ist eine vertretbare. Vollautomatisches Aufräumen, wie man es in C++ gewohnt ist, wäre in Java selbst dann nicht einführbar, wenn man Bedenken über Rückwärtskompatibilität ignorierte, weil Objekte dort Referenztypen sind und nicht automatisch am Ende des Blocks ihre Gültigkeit verlieren müssen. Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Im Übrigen rate ich dir, dich vor einer Fortsetzung dieser Diskussion über die Funktionsweise generationeller Garbage-Kollektoren zu informieren (diese sind in JVMs üblich).



  • seldon schrieb:

    Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Wenn ich Ringkonstrukte hab, dann hab ich sowieso ganz andere, schlimmere Probleme 😉


Anmelden zum Antworten