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! 🙂


Log in to reply