Template-Klasse, Problem mit Vektor



  • Hallo zusammen,

    ich versuche mich gerade an einer Template-Klasse, scheitere aber irgendwie hoffnungslos 🙂
    Ich arbeite mit dem Visual Studio 2008 und entsprechendem Compiler.

    Die Klasse (alles in einem Header-File):

    #pragma once
    
    #include <vector>
    
    template <class T>
    class BBGarbageCollector
    {
    public:
    	BBGarbageCollector(void);
    	~BBGarbageCollector(void);
    
    	void Add(T* e);
    	void DeleteGarbage();
    
    protected:
    	std::vector<T*> m_vecGarbage;
    };
    
    template <class T>
    BBGarbageCollector<T>::BBGarbageCollector(void)
    {
    }
    
    template <class T>
    BBGarbageCollector<T>::~BBGarbageCollector(void)
    {
    }
    
    template <class T>
    void BBGarbageCollector<T>::Add(T *e)
    {
    	m_vecGarbage.push(e);
    }
    
    template <class T>
    void BBGarbageCollector<T>::DeleteGarbage()
    {
    	for(std::vector<T*>::iterator it = m_vecGarbage.begin(); it != m_vecGarbage.end(); it++)
    	{
    		delete it->second;
    	}
    
    	m_vecGarbage.clean();
    }
    

    Generiert bei der Instanzierung (hier am Beispiel von BBParticle) beim Kompiliervorgang Fehler in diesem Stil:

    1>BBParticle.cpp
    1>d:\vs\bbce\bbgarbagecollector.h(33) : error C2039: 'push' : is not a member of 'std::vector<_Ty>'
    1>        with
    1>        [
    1>            _Ty=BBParticle *
    1>        ]
    1>        d:\vs\bbce\bbgarbagecollector.h(32) : while compiling class template member function 'void BBGarbageCollector<T>::Add(T *)'
    1>        with
    1>        [
    1>            T=BBParticle
    1>        ]
    1>        d:\vs\bbce\bbparticle.cpp(40) : see reference to class template instantiation 'BBGarbageCollector<T>' being compiled
    1>        with
    1>        [
    1>            T=BBParticle
    1>        ]
    1>BBParticleSpray.cpp
    

    Das ganze verwirrt mich etwas, ich sehe meinen Fehler nicht...

    Danke im Voraus!

    PS: Wer den Fehler findet, darf auch meinen Coding-Stil bemängeln soviel er will xD



  • meiste push_back statt push?



  • danke volkard!

    zuviel java tut nicht gut 😉



  • ok, dann darf ich jetzt meckern:
    erstens:
    das void in

    BBGarbageCollector(void);
    

    ist uncool.

    ups, gibt gar kein zweitens.



  • hm... ist ein problem, welches ich bei dem projekt sowieso habe!
    das ding ist schon älter. entweder implementiere ich nun alles wie begonnen ( (void), ErsterCamelCaseBuchstabeGrossStattKlein ) oder habe ein durcheinander.
    code aufräumen ist keine alternative, da faul.

    und das kein zweitens nehm' ich als kompliment 🙂



  • das_brot schrieb:

    hm... ist ein problem, welches ich bei dem projekt sowieso habe!
    das ding ist schon älter. entweder implementiere ich nun alles wie begonnen ( (void), ErsterCamelCaseBuchstabeGrossStattKlein ) oder habe ein durcheinander.
    code aufräumen ist keine alternative, da faul.

    bist du in der lage, ein tool zu benutzen, das über alle files (void) durch () ersetzt? und dann den compiler starten und die extrem seltenen fälle heilen, wo ein (void) doch hingehört.

    das_brot schrieb:

    und das kein zweitens nehm' ich als kompliment 🙂

    war auch genau so gemeint. 🙂



  • volkard schrieb:

    ups, gibt gar kein zweitens.

    Doch, zum Beispiel m_vecGarbage.clean(); ... 😉

    das_brot, ich würde aufpassen, dass du nicht versuchst, Konzepte anderer Programmiersprachen 1:1 zu übertragen. In C++ gibt es eigentlich keine zentralen Garbage-Collectors, durch RAII ist eigentlich jede Klasse selber dafür zuständig, dass sie aufgeräumt ist. Container handhaben die Speicherverwaltung sowieso selbst, Smart Pointer können dich bei RAII unterstützen.

    Als Experiment ist das natürlich okay, aber ich würde vorsichtig sein, sowas in der Praxis breit anzuwenden...



  • Nexus schrieb:

    Doch, zum Beispiel m_vecGarbage.clean(); ... 😉

    ist natürlich mittlerweile ein clear() ^^

    Wegen Sinn und Unsinn des GC: Der Code ist Teil einer Spieleengine.
    Da existieren nun etwa Partikel, welche als grafische Objekte durch die Spielwelt schwirren und eine Lebzeit haben. Das Partikel wird als normales Grafikobjekt animiert und merkt selbst, wenn seine Lebzeit überschritten ist. Seine Ressourcen sollen nun also wieder freigegeben werden. "delete this" ist kein Ansatz, weshalb es sich beim GC einträgt.
    Dieser wiederum löscht dann alle paar Frames die eingetragenen Objekte.

    Ist der Begriff GC in diesem Zusammenhang unglücklich gewählt?
    Oder habt ihr da bessere Ansätze? Fänd' ich spannend, die zu hören!

    //Edit:
    Und weis wer ein Refactoring-Tool, welches mir gleich alle Members von CamelCase nach camelCase umbenennen kann? Dann mach' ich sogar noch die (void)s im gleichen Zug weg 😛

    Gruss



  • das_brot schrieb:

    Oder habt ihr da bessere Ansätze? Fänd' ich spannend, die zu hören!

    Ich würde hier direkt STL-Container oder -Adapter verwenden.

    Haben die Partikel alle die gleiche Lebenszeit? Falls ja, kannst du nämlich eine std::queue oder (um mehr Flexibilität zu haben) std::list bzw. std::deque einsetzen. Du hängst neue Partikel am Schluss an, und löschst am Anfang solange, wie das erste Element seine Lebzeit überschritten hat. Also ganz nach dem FIFO-Prinzip einer Warteschlange.

    Falls die Zeiten unterschiedlich sind, böte sich eventuell std::priority_queue oder direkt ein Random-Access-Container wie std::vector bzw. std::deque an. Mit std::remove_if() könntest du Elemente, die tot sein müssten, nach hinten verschieben und anschliessend mit erase() löschen.

    Wenn dir das ganze noch nicht ganz klar ist, frag einfach wieder. Vielleicht hilft dir auch www.cplusplus.com mit der Dokumentation der oben genannten Klassen und Funktionen. 😉



  • Die LZ ist unterschiedlich.

    Den Ansatz, von aussen zu überprüfen wer tot ist und danach zu löschen, habe ich wieder verworfen.
    Grund: Wenn ich viele Objekte habe, will ich die aus Performancegründen nicht alle paar Frames durchpollen, um dann vielleicht 0.1% der Objekte zu löschen.
    Deshalb liegt die Initiative beim Objekt, welches sich aktiv als Löschkandidat beim GC einträgt.

    Macht Sinn, oder? 🙂



  • das_brot schrieb:

    Deshalb liegt die Initiative beim Objekt, welches sich aktiv als Löschkandidat beim GC einträgt.

    Aber woher weiss dann das Objekt, dass es gelöscht werden muss? Da musst du ja auch durchiterieren.



  • Okeee. Deal 😃

    Ich hab' sowas:

    //LZ = Laufzeit
    class LZObjekt : Objekt;
    
    Engine::Weiter()
    {
      for(Objekt : AlleObjekte)
      {
        //Ist das Objekt ein LZObjekt, checkt es im Verlauf von Weiter() seine LZ und trägt sich wenn nötig im GC ein
        Objekt->Weiter();
      }
    
      GC->DeleteObjects();
    }
    

    D.h. so erledigt die Polymorphie den Check für mich, ich muss also nicht a) ein Zweites mal über alle LZObjekte iterieren (resp mache die erst im GC) oder b) in der for-Schleife auf ein LZ-Objekt prüfen.

    => Grundsätzlich ist ja der Unterschied, dass sich das Objekt bei meinem Ansatz "von innen" zur Löschung vermerkt, während du lieber "von aussen" feststellen würdest, ob es gelöscht werden soll. Warum?



  • Ich hab nicht grundsätzlich was dagegen, "von innen" zu löschen. Bei deinem Code frage ich mich gerade, was ist AlleObjekte für ein Container? Benutzt du überhaupt Standard-C++ (wegen der komischen For-Syntax)?

    Denn das Löschen per delete reicht ja allein nicht, im Container AlleObjekte sind die Zeiger nach wie vor gespeichert. Und wie willst du die entfernen?

    Wenn du beispielsweise eine std::list benutzt, kannst du auch "von innen" Elemente löschen. Um Genaueres zu sagen, müsste ich mehr über den zu Grunde liegenden Container wissen.

    std::list<Object> List;
    
    for (std::list<Object>::iterator i = List.begin(); i != List.end(); )
    {
        i->Update();
    
        if (i->IsDead())
            i = List.erase(i);
        else
            ++i;
    }
    


  • Ist pseudocode, ich bin auf der Arbeit, und das Ding ist zuhause.

    Aber du hast Recht, das Problem mit den "kaputten Zeigern" in AlleObjekte wird mir nen Strich durch die Rechnung machen, da muss ich wohl auf die Überprüfung in der Schleife ausweichen.

    Das andere, ursprüngliche und sinnvollere, Einsatzgebiet für den GC sind Positionen. Diese werden von mehreren Objekten gebraucht und zählen die Referenzen. Sobald sich alle Objekte "abgedockt" haben, lässt sich die Position vom GC löschen. Hier denke ich, mit dem GC eine gute Lösung gefunden zu haben.

    Beides ist soweit nur graue Theorie, die Klasse und die zu verwaltenden Objekte stehen, aber zusammengefügt ist das Ding noch nicht wirklich...



  • das_brot schrieb:

    Das andere, ursprüngliche und sinnvollere, Einsatzgebiet für den GC sind Positionen. Diese werden von mehreren Objekten gebraucht und zählen die Referenzen. Sobald sich alle Objekte "abgedockt" haben, lässt sich die Position vom GC löschen. Hier denke ich, mit dem GC eine gute Lösung gefunden zu haben.

    Hier scheint shared_ptr eine gute Alternative zu sein. Es kann schon sein, dass dein GC-Konstrukt in gewissen Fällen Sinn macht. Ich habe sowas nur selbst nie gebraucht und kann mir den Nutzen davon nicht wirklich vorstellen (ich hasse nebenbei Frameworks mit automatischer Referenzzählung, z.B. die GUI-Bibliothek VCF). Ich habe es lieber, ich habe mehr Kontrolle über die Lebensdauer meiner Objekte. Mit RAII, Containern und Smart Pointers werden Elemente automatisch zerstört, sobald sie aus dem Scope gehen.

    das_brot schrieb:

    Aber du hast Recht, das Problem mit den "kaputten Zeigern" in AlleObjekte wird mir nen Strich durch die Rechnung machen, da muss ich wohl auf die Überprüfung in der Schleife ausweichen.

    Musst du überhaupt Zeiger in dem Container speichern? Womöglich wären direkte Objekte einfacher.

    Es gibt viele Möglichkeiten, effizient aus Containern zu löschen. Eine verkettete Liste ist diesbezüglich wahrscheinlich am schnellsten, da du nur einmal durchiterieren musst und problemlos Elemente mitten drin löschen kannst. Vermute ich - wenn es wirklich auf Performance ankommt, sollte man einen Profiler zu Rate ziehen.

    Was hast du jetzt für einen Containertypen?



  • Hab' mir shared_ptr angesehen, scheint wirklich sinnvoll! Werde ich zuhause mal ausprobieren.

    Was den Container angeht, kann ich das gerade nicht sagen (alter Code...), dürfte aber ein Vektor mit ID als Key sein (zwecks wiederfinden bestimmter Objekte, ob's nötig ist, werde ich noch rausfinden...).
    Pointer machen in meinem Fall Sinn, da die Objekte in verschiedenen Layern abgelegt werden (das AlleObjekte oben bezieht sich auf alle Objekte in einem bestimmten Layer), und auch zwischen diesen wechseln können sollen.

    Danke für all den Input! Einiges werd' ich sicher übernehmen. 👍



  • das_brot schrieb:

    Pointer machen in meinem Fall Sinn, da die Objekte in verschiedenen Layern abgelegt werden (das AlleObjekte oben bezieht sich auf alle Objekte in einem bestimmten Layer), und auch zwischen diesen wechseln können sollen.

    Genau dafür ist boost::shared_ptr ideal (evtl. auch std::tr1::shared_ptr , je nach TR1-Unterstützung). Du kannst damit in mehreren Containern Verweise auf ein einzelnes Objekt haben und dieses somit teilen ("share"). Sobald du den letzten Zeiger löschst, wird das Objekt automatisch zerstört und der Speicher freigegeben. Ohne dass du dich jemals darum kümmern musst.

    das_brot schrieb:

    Danke für all den Input! Einiges werd' ich sicher übernehmen. 👍

    Kein Problem, das mach ich gerne. Wenn du wieder Fragen hast, stelle sie einfach! 🙂


Anmelden zum Antworten