Garbage Collector



  • AlexanderKiebler schrieb:

    Also welche Daten werden wo gespeichert ??

    Adresse des Speicherbereichs im Pointer. Ende.



  • AlexanderKiebler schrieb:

    Wenn du dich mit dem Problem so gut auskennst, hast du dich damit also auch befasst ??

    Ja.

    Ergo du bist nach deiner Aussage ein schlechter Programmierer geworden was C++ an geht ??

    Nein. Eher so Mittelmaß. Aber ich habe genug Erfahrung und lange mit "Spezialisten" zusammengearbeit, um Mist zu erkennen.

    Wiso sollt ich dann auf dich hören ??

    Brauchst du doch nicht.

    Ratschläge sind ja gut, aber sie sollten doch eher ohne Wertung auf die Persohn stattfinden.

    Leg dir mal ein dickeres Fell zu. Du hast ein Problem mit C++, du kriegst Antworten darauf. Nicht jede dieser Antworten muss dir gefallen.

    Wenn dich das "Idiot" persönlich getroffen hat, entschuldige ich mich für die Bezeichnung und ändere es hiermit in "Ahnungsloser".



  • Wie würdest du denn das Problem angehen ??
    P.s.: Würd mich über ne Antwort ohne Beschimpfung freuen 🙂



  • Ich werde aus deiner Beschreibung nicht so recht schlau und bin mir deshalb nicht sicher was genau das Problem ist.

    Generell würde ich sagen: Speicherverwaltung in C++ ist einfach, wenn man zwei einfache Grundsätze befolgt:
    1. Der Teil des Programms, der den Speicher anfordert, gibt ihn auch wieder frei.
    2. RAII funktioniert (fast) immer.

    Alles was hiervon abweicht, ist erst mal komisch und sollte gut begründet werden.



  • AlexanderKiebler schrieb:

    Wie würdest du denn das Problem angehen ??

    Da es eh eine Möglichkeit geben muß, herauszufinden, welche Objekte noch leben, also auf welche Objekte noch Zeiger zeigen, müssen die Zeiger selber smart werden. Ich denke mal, gegen dieses Argument kannste keinen Ausweg finden.
    Und dann kannste auch den Großteil der Mitzähl-Und-Aufräum-Logik in die smart pointers legen. Wenn GC-Objekte eh von der Basisklasse GarbageCollect erben müssen, was eine sehr gute Idee ist, brauchste vielleicht gar nicht an new/delete zu fummeln, dann kannste den refcounter oder die Verkettungszeiger vielleicht in der Basisklasse lassen. Draußen wäre vielleicht lecker, um mal eine XOR-Liste auszuprobieren.



  • Ich verstehe das Problem noch nicht ganz. Während einer Simulation werden also dynamisch Objekte erzeugt, die -- wahrscheinlich -- ein paar Zeitschritte überleben sollen und dann irgendwann wieder zerstört werden sollen. Richtig soweit? Warum siehst Du dich jetzt gezwungen, einen GC nachzubauen?



  • Hi Volkard,

    Erstmal Danke Für die Antwort. 🙂
    Also

    Da es eh eine Möglichkeit geben muß, herauszufinden, welche Objekte noch leben, also auf welche Objekte noch Zeiger zeigen, müssen die Zeiger selber smart werden.

    Ich hab mir das gerade mal durchgeguckt. Also die Smart Pointer von Boost.
    Ich persöhnlich halte das ein bischen für "mit Kanonen auf Spatzen geschossen"

    Testen auf:
    -Gibt es noch einen Pointer auf das Objekt
    Zum auslösen von delete ist schon mehr als ich jetzt wollte.

    Was ist denn so schlecht daran einfach new und delete zu überladen ??
    Ist das geächtet wie goto ??

    In der Funktion
    MY_MAIN(void)
    kann man dann so programmieren wie immer. Nur wenn zufällig ein Pointer nicht aufgeräumt wird, wird das eben automatisch erledigt.

    Ich sehe bei meiner vorgehensweise eigentlich kein Problem.

    Wobei cih das mit den Smart pointern sehr schlau finde =).



  • @krümel

    Also gezwungen nicht. Aber es wäre eben schön.
    Die Simulationsblöcke sollen alle in einer Funktion
    MY_MAIN(void)
    erzeugt werden.
    Damit sie nach dem Verlassen der Funktion noch existiern sollte das Dynamisch geschehen.
    Jeder Simulationsblock lebt bis zum Ende der Simulation.
    Und dann sollen sie aber zerstört werden.

    Ich glaube das Wort Garbage Collector ist etwas hochgegriffen gewesen von meiner Seite.
    Der soll ja eigentlich prüfen ob noch ein Pointer auf die Instanz vorhanden ist. Und wennn nein, dann soll er das Objekt zerstören.
    Das brauch ich garnicht.
    Ich muss einfach alles was in der Funktion
    MY_MAIN(void)
    dynamisch erzeugt, und nicht mit delete zerstört worden ist dannach aufräume.
    Wobei cih davon ausgehen darf, dass in dieser Funktion alle Objekte von
    der Simulationsklass und damit von der Garbage Collect klasse erben.



  • Ein Problem dabei ist, dass C++ dem Programmierer ein bestimmtes Verhalten zusagt. Dazu gehört, dass ein mit new geholter Pointer bis zum delete gültig bleibt. Du greifst mit deinem Konzept in dieses System ein und setzt damit den gesamten C++-Standard außer Kraft. Wenn mal jemand dein System benutzt, der mehr von C++ versteht als du, der wird dich verfluchen.

    Außerdem ist es eine üble Sache, Programmierfehler zu verstecken. Du bekämpfst damit Symptome schlampiger Programmierung, statt die Ursache zu beheben. Gib statt dessen eine fette Fehlermeldung aus, das macht es ein klein wenig besser (aber immer noch nicht gut).

    Und dann die ganzen Fallen. Es gibt Geschichten wie placement new, die dir hier in die Suppe spucken könnten. Möglicherweise weißt du nicht einmal, was das überhaupt ist - das wäre dann ein sicheres Zeichen für dich, nicht in der Speicherverwaltung rumzupfuschen.
    Und natürliche globale Objekte, die im Destruktor selbst ein delete machen, wobei dein Gehacke sofort übelst krachen geht, mit einem Programmabsturz, dessen Ursache kaum zu finden ist.



  • Also zunächst bleib ich dem verhalten von C++ treu, soweit es ein GUI programmierer kennt. Das passt eigentlich schon.
    Der Programmierer Darf zerstören, muss aber nicht, so wie in den Meißten Bibliotheken.

    Ein Placement new würd ich hier jetzt nicht implementieren.
    Das wäre eher interessante, wenn ich zeitkritisch was alloziieren möchte,
    oder auch für objekte in Shared Memory könnt ich mir das vorstellen.
    Placement new wird bei mir nicht vorkommen.

    Gruß



  • AlexanderKiebler schrieb:

    Also zunächst bleib ich dem verhalten von C++ treu, soweit es ein GUI programmierer kennt. Das passt eigentlich schon.

    ...was soll man auf solche Ignoranz noch antworten? Du bleibst damit dem Verhalten von C++ ungefähr so treu, wie dein Geschreibsel der Deutschen Rechtschreibung treu bleibt. Also quasi überhaupt nicht.

    Der Programmierer Darf zerstören, muss aber nicht, so wie in den Meißten Bibliotheken.

    Doch, der Programmierer muss, weil er korrekt programmieren soll. Und wenn du ihm in die Quere kommst, dann darf er eben nicht mehr. Siehe globale Objekte.

    Ein Placement new würd ich hier jetzt nicht implementieren.

    Ach.

    Ich lasse es mal gut sein hier in dem Thread. Vielleicht findet ein anderer bessere Argumente, die du verstehen kannst 🙂



  • AlexanderKiebler schrieb:

    Ich hab mir das gerade mal durchgeguckt. Also die Smart Pointer von Boost.
    Ich persöhnlich halte das ein bischen für "mit Kanonen auf Spatzen geschossen"

    Du musst bei einem GC ja auch darüber eine Liste führen, wenn der Zeiger noch verwendet wird, und dies geht nur mittels Mechanismen wie Referenzzählung. Zudem ist der shared_ptr, obwohl ein Schwergewicht (Den ich nur bei geteilten Ressourcen verwenden würde), auch in der Lage gegenseitige Objektverweise aufzulösen (Stichwort: weak_ptr). Mal sehen wie du das ohne den Aufwand nachbasteln willst.

    GC und C++ vertragen sich wirklich nicht, zumindest nicht, wenn nicht Auflagen zur Verwendung oder noch besser der Compiler eine Lösung unter der Haube einbaut (Ist ja durchaus "optional" in Überlegung für einen späteren Technical Report).



  • Also ich habe mal ein Testprogramm geschrieben wo zeigt, wie cih das meine.
    Es müßten noch die Operatoren new[] und delete[] überschreben werden, welche aber eine neue statische liste bekommen würden. Und die richtige Ausnahme schmeißen...
    Hier die main.cpp

    #include <iostream>
    
    #include "TestClass.h"
    
    int main(void)
    {
    
        TestClass* a=new TestClass;
        a=new TestClass;
        a=new TestClass;
        a=new TestClass;
        delete a;
        a=new TestClass;
        a=new TestClass;
    
        a->remove_all_instances();
    
        return 0;
    }
    

    Dann die klasse zum testen welche von GaCo abgeleitet wurde.

    #ifndef TEST_CLASS_H
    #define TEST_CLASS_H
    
    #include "GaCo.h"
    
    class TestClass: public GaCo
    {
        public:
            TestClass(){std::cout <<"\n Construkt TestClass";};
    
    };
    
    #endif
    

    Und zuletzt der GaCo =):

    #ifndef GA_CO_H
    #define GA_CO_H
    
    #include <iostream>
    
    #include <set>
    
    class GaCo
    {
        public:
            /// Zerstört alle noch vorhandenen Objekte
            void remove_all_instances(void);
            ///Operatoren
            void * operator new (size_t s)throw();
            void operator delete (void * ptr);
    
        private:
            static std::set<void *> instances;
            static std::set<void *>::iterator instances_it;
    
    };
    
    #endif
    

    und

    #ifndef GA_CO_CPP
    #define GA_CO_CPP
    
    #include "GaCo.h"
    #include <cstdlib>
    
    std::set<void *> GaCo::instances;
    std::set<void *>::iterator GaCo::instances_it;
    
    void GaCo::remove_all_instances(void)
    {
    
        instances_it=instances.begin();
        while(instances_it!=instances.end())
        {
            std::cout <<"\n ptr \t" <<*instances_it;
            free(*instances_it);
            instances_it++;
        }
        instances.clear();
        std::cout <<"\n instances.size()\t" <<instances.size();
    }
    
     void * GaCo::operator new(size_t s)throw()
     {
        std::cout <<"\n\noperator GaCo::new call";
        void* ptr=std::malloc(s);
        if(ptr)
            instances.insert(ptr);
        else
        {
            ///Schmeißen
        }
        std::cout <<"\n instances.size()\t" <<instances.size();
        std::cout <<"\n ptr \t" <<ptr;
        return ptr;
     }
    
     void GaCo::operator delete(void *ptr)
     {
         std::cout <<"\n\noperator GaCo::delete call";
        instances_it=instances.find(ptr);
        if(instances_it!=instances.end())
            instances.erase(instances_it);
        free(ptr);
    
        std::cout <<"\n instances.size()\t" <<instances.size();
        std::cout <<"\n ptr \t" <<ptr;
     }
    
    #endif
    

    Läuft einwandfrei muss ich sagen.
    Für Rechtschreibung, sorry, ich achte nicht gerade drauf. 😞



  • AlexanderKiebler schrieb:

    Also ich habe mal ein Testprogramm geschrieben wo zeigt, wie cih das meine.

    Also unter einem GC verstehe zumindest ich eindeutig etwas anderes: Zum Beispiel löscht ein GC nur Objekte die nicht mehr verwendet werden. Davon abgesehen das ich Iteratoren nur lokal anlegen würde.



  • Ja ich mittlerweile auch. Wie bereits mehrmals gesagt, ist der Begriff Garbage Collector zu viel gesagt.

    Dort müßt ich ja immer schauen ob noch ein Pointer auf mein objekt vorhanden ist, und dann löschen =).

    habs schon öfters geschriebenhier, dass ich das nocht möchte.

    Gruß
    P.s.: Das wäre auch ne Menge Arbeit denke ich 🙂



  • AlexanderKiebler schrieb:

    P.s.: Das wäre auch ne Menge Arbeit denke ich 🙂

    Oder man verwendet shared_ptr/weak_ptr... 😉



  • Bald soll ja neues C++ raus kommen 🙂
    Freu mich schon riesig drauf. Meinst da ist ein Garbage Collector mit drin ??
    Soll einiges von boost übernommen werden habe ich gehört.



  • AlexanderKiebler schrieb:

    Bald soll ja neues C++ raus kommen 🙂
    Freu mich schon riesig drauf. Meinst da ist ein Garbage Collector mit drin ??

    Nein, es ist kein GC enthalten. Es ist zwar ein optionaler GC langfristig angedacht (aber einer nach der Art: entweder für das ganze Programm, oder garnicht), aber nicht mehr für den Standard.

    Wobei ich unter C++ keinen GC verwenden würde (unabhängig davon, das ich GCs nicht ablehne, nur passt es nicht zur C++ Programmierung).



  • AlexanderKiebler schrieb:

    Was ist denn so schlecht daran einfach new und delete zu überladen ??
    Ist das geächtet wie goto ??

    Nein. Das zu Überladen ist schon ok.
    Aber das, was Du vor hast, kann nicht rundum funktionieren. Du wirst eine Frickellösung erhalten, die immer mit massig Ausnahmen une Einschränkungen leben muß. Große Kosten, aber praktisch kein Zugewinn. Zum Beispiel habe ich eine Klasse Button, die hat innendrin einen Zeiger auf das ihn besitzende Window. Im Button-Destruktor wird dem Window noch schnell bescheid gesagt. Vergißt der User, den Button zu löschen, ist das ein Fehler und der User muß bestraft werden.
    Bei Dir hingegen wird der Button dann einfach nach der main gelsöcht, aber oh weh!, das Window ist ja schon lange weg und Du sagst totem Speicher bescheid. Das ist Abstürzen auf die umständliche Art.

    Mach doch statt dem Aufräumer einen Auflister! Der Listet alle Speicherbereiche auf, die zu löschen vergessen wurden! Der User muß dann halt sein Speicherloch suchen und dichtmachen.



  • AlexanderKiebler schrieb:

    Läuft einwandfrei muss ich sagen.

    Bei dir laufen gar keine Destruktoren. Du nimmst den Objekten einfach den Speicher weg, ohne sie zu "destruieren". Bei PODs kann man das so machen. Aber auch nur dann. Das hier klappt schon nicht mehr:

    struct ich_bin_kein_pod
    {
      std::string name;
      int geburtsjahr;
    };
    

    und Du bekommst Speicherlecks ohne Ende.

    Ich sehe -- wie jemand vorher auch schon erwähnt hat -- keine Notwendigkeit hier mit Überladung von new/delete "rumzufrickeln". Du leitest ja eh zu dem Zweck von einer Klasse ab. Und diese Klasse könnte ebenso gut im Konstruktor und Destruktor sich "irgendwo" anmelden und abmelden. Ein virtueller Destruktor ist da wahrscheinlich auch nicht verkehrt.

    Hier eine Skizze:

    #include <cassert>
    #include <utility>
    #include <type_traits>
    #include <boost/noncopyable.hpp>
    
    struct links : private boost::noncopyable
    {
    	links* prev;
    	links* next;
    	links() : prev(0), next(0) {}
    	void isolate() {
    		if (prev) prev->next = next;
    		if (next) next->prev = prev;
    		prev = next = 0;
    	}
    	~links() {isolate();}
    };
    
    void insert_after(links & l1, links & l2)
    {
    	assert(!l2.prev);
    	assert(!l2.next);
    	l2.next =  l1.next;
    	if (l1.next) l1.next->prev = &l2;
    	l2.prev = &l1;
    	l1.next = &l2;
    }
    
    class collectable : links
    {
    	friend class collector;
    protected:
    	collectable() {}
    public:
    	virtual ~collectable() {}
    
    	bool owned() const {return prev;}
    
    	void break_free() {this->isolate();}
    };
    
    class collector : links
    {
    public:
    	template<class T, class...Args>
    	typename std::enable_if<(
    		std::is_convertible<T*,collectable*>::value),
    	T* >::type cnew(Args&&...args)
    	{
    		T* p = new T(std::forward<Args>(args)...);
    		collectable* q = p;
    		insert_after(*this,*q);
    		return p;
    	}
    
    	void take_over(collectable* t)
    	{
    		t->isolate();
    		insert_after(*this,*t);
    	}
    
    	~collector();
    };
    
    collector::~collector()
    {
    	while (next) {
    		collectable* const tmp = static_cast<collectable*>(next);
    		delete tmp; // <-- mutates next
    		assert(next != tmp);
    	}
    }
    
    #include <iostream>
    
    struct foo : collectable
    {
    	int number;
    	explicit foo(int i) : number(i) {}
    	~foo() {std::cout << "~foo, " << number << '\n';}
    };
    
    int main()
    {
    	collector c;
    	foo* p = c.cnew<foo>(23);
    	foo* q = c.cnew<foo>(1729);
    	foo* z = new foo(42);
    	c.take_over(z);
    	delete p; // fine, Objekt is removed from c's list
    	collector d;
    	d.take_over(q); // steals *q from collector c
    } // <-- *q and *z will be destroyed here
    

    Die Objekte, auf die p und q ind z zeigen "gehören" höchstens einem "Collector". Man kann sie manuell löschen oder von den Collectoren löschen lassen, wenn die Collectoren zerstört werden.

    So etwas ähnliches machen die glaub'ich auch bei Qt -- also eine beidseitige Registrierung zwischen Besitzer und Besitz, so dass ein manuelles Löschen beim Besitzer auch automatisch gemeldet wird.

    BTW: C++0x bringt von Haus aus keine Garbage-Collection mit.

    kk


Anmelden zum Antworten