Abstrakte Klasse in std::map



  • Hey,
    ich bastel hier gerade an einem kleinen Spielchen und habe mir folgende (abstrakte) Basisklasse für alle Spielobjekte gebaut:

    #ifndef __JEIX_OBJ_BASE_HPP__
    #define __JEIX_OBJ_BASE_HPP__
    
    #include <map>
    #include <SFML/Graphics.hpp>
    #include "manager.hpp"
    
    // forwad declaration
    class Manager;
    
    class GameObject
    {
    	Manager&   _manager;
    	sf::Sprite _sprite;
    
    public:
    	GameObject(Manager& manager) : _manager(manager) {}
    	virtual void think() = 0;
    	virtual sf::Sprite& getSprite() { return _sprite; }
    };
    
    #endif
    

    Ich hatte mir nämlich gedacht, dass ich davon die einzelnen Charaktere ableiten könnte, um die dann in eine std::map<std::string, GameObject> oder so zu packen. Aber das geht wohl so direkt nicht, denn bei folgender Konstellation: std::map<std::string, GameObject> ; gibt er mir einen Fehler, dass man keine Instanz von einer abstrakten Klasse erstellen könne. Nun gut, dass man eine abstrakte Klasse nicht instanzieren kann, ist mir bewusst, aber wieso er bei der Variablendeklaration versucht, davon schon eine Instanz zu erstellen ist mir schleierhaft :[.

    Nun zu meiner eigentlich Frage, wie würdet ihr das lösen? Zwei Ansätze, die mir da grad spotan einfallen wären, einfach die Map so zu definieren: std::map<std::string, GameObject*> und mit Pointern arbeiten, was mir aber eigentlich weniger gefällt -> Pointer. Und der andere wäre eine kleine Wrapper Klasse zu erstellen alâ GameObjectHandle, die einen GameObject Zeiger enthält. Siehe hier: http://www.gamedev.net/community/forums/viewreply.asp?ID=2630441 der Post von Zahlman

    Zum Schluss könnte man auch einfach die Klasse nicht abstrakt machen, dagegen spricht jedoch, dass man sie eigentlich nicht zum instanzieren gedacht ist und nur die Schnittstelle think() definiert, die jedes Objekt haben sollte. Wie würdet ihr das lösen? btw: sonstige Anmerkungen zum Code werden auch gern angenommen. 🙂



  • Nun zu meiner eigentlich Frage, wie würdet ihr das lösen? Zwei Ansätze, die mir da grad spotan einfallen wären, einfach die Map so zu definieren: std::map<std::string, GameObject*> und mit Pointern arbeiten, was mir aber eigentlich weniger gefällt -> Pointer.

    Nö, genau der Richtige Ansatz.Wenn dein GameObject eh als abstrakte Klassen dienen soll, musst du Pointer verwenden um die Laufzeitpolymorphie zu gewährleisten, was ja meistens der Sinn einer abstrakten Klasse ist.



  • Jetzt kommen meine Wissenslücken durch. Ich hab hier mal ein kleinen Beispiel-Code geschrieben, einmal mit eigenhändiger Speicherverwaltung und einmal mit den boost::shared_ptr-Pointern. Aber ich weiß nicht, ob da auch wirklich keine Speicherlecks enstehen, weil ich mir bei den STL-Containern nicht sicher war, wie ich da den Speicher freigeben kann, so dass der auch wirklich freigegeben ist und der Container auch nachher alles ordentlich aufräumt. Und bei Boost war ich mir nicht sicher, ob das so richtig angewendet ist. 😕

    Hier der Code:

    #include<iostream>
    #include<string>
    #include<map>
    #include<boost/shared_ptr.hpp>
    
    /*
     *
     *   test.cpp
     *
     */
    
    class Base
    {
    	int foo;
    	float bar;
    
    public:
    	virtual ~Base() {}
    	virtual void print() = 0;
    };
    
    class Drucker : public Base
    {
    public:
    	void print() { std::cout << "Printed.." << std::endl; }
    };
    
    class Scanner : public Base
    {
    public:
    	void print() { std::cout << "Scanned.." << std::endl; }
    };
    
    typedef std::map<std::string, Base*> List;
    typedef boost::shared_ptr<Base> BasePtr;
    typedef std::map<std::string, BasePtr> BList;
    
    int main()
    {
    	/*
    	 *   manuelle Speicherverwaltung:
    	 */
    	List list;
    	list["drucker"] = new Drucker();
    	list["scanner"] = new Scanner();
    
    	List::iterator it;
    	for(it=list.begin();it!=list.end();it++)
    	{
    		it->second->print();
    		delete (it->second); // so okay?
    	}
    	list.clear();
    	std::cout << list.size() << std::endl;
    
    	/*
    	 *   boost::shared_pointer
    	 */
    	BList blist;	
    	blist["drucker"] = BasePtr(new Drucker()); // ist das eigentlich erlaubt, wegen temporärem shared_pointer?
    	blist["scanner"] = BasePtr(new Scanner());
    
    	BList::iterator bit;
    	for(bit=blist.begin();bit!=blist.end();bit++)
    	{
    		bit->second->print();
    	}
    	blist.clear();
    	std::cout << blist.size();
    
    	std::cin.ignore();
        return 0;
    }
    

    *Bitte nicht hauen, ich wollte lieber fragen, als später Speicherlecks zu haben*



  • Also das hier:

    BasePtr(new Drucker())
    

    Ist nicht notwendig. Ein einfaches new Drucker() reicht da komplett aus.Ansonsten sieht der Rest ganz gut aus, bis auf deine Benennung natürlich, weil List lässt auf einen ganz anderen container schließen, als der der bei dir eigentlich dahinter liegt.


  • Administrator

    Firefighter schrieb:

    Also das hier:

    BasePtr(new Drucker())
    

    Ist nicht notwendig.

    Doch, ist es. Die Konstruktoren von Boost.SharedPtr sind explicit . Der operator = nimmt nur ein Objekt gleichen Typs an, also keinen Zeiger. Eine implizite Umwandlung gibt es somit nicht und wollte man auch bewusst verhindern.

    Lies vielleicht auch gleich mal dies (und zwar issen1, wie auch Firefighter):
    -> http://www.boost.org/doc/libs/1_39_0/libs/smart_ptr/shared_ptr.htm#BestPractices

    @issen1,
    Dies wäre vielleicht auch noch etwas praktisches für dich:
    http://www.boost.org/doc/libs/1_39_0/libs/ptr_container/doc/ptr_container.html

    Bzw. zum Beispiel die ptr_map :
    http://www.boost.org/doc/libs/1_39_0/libs/ptr_container/doc/ptr_map.html

    Dieser Container löscht deine Objekte automatisch per delete . Sowas ist deutlich effizienter als ein Boost.SharedPtr in einer STL Map.

    Grüssli



  • BList blist;   
    blist["drucker"] = BasePtr(new Drucker());
    blist["scanner"] = BasePtr(new Scanner());
    

    (edit: hab das falsche mal entfernt)

    Gut, auf jeden Fall danke, ich werde das mal umsetzen. Sollte ich dann lieber die Smart Pointer nutzen oder doch selber den Speicher verwalten? Und ja, List war unglücklich gewählt, ist aber ja auch nur ein Beispiel gewesen ;).

    Edit auf Dravere: Deswegen hatte ich es auch erst so gemacht und ich werde mir die ptr_map mal anschauen.



  • @dravere, danke dir. Den explizit ctor hatte ich ganz vergessen.



  • issen1 schrieb:

    ...
    Sollte ich dann lieber die Smart Pointer nutzen oder doch selber den Speicher verwalten?

    Wenn du die Daten nur und ausschließlich an einer Stelle verwaltest, würde ich zu den ptr-Containern raten. Wenn du die Daten aber verteilst, und der letzte "das Licht ausmachen soll", sind shared_ptr die bessere Wahl.



  • Okay, ich denke ich werde dann die ptr_map verwenden. Kann man bei dieser eigentlich nicht den operator[] benutzen? Dann bringt er mich nämlich den Fehler, dass zB. Scanner* nicht in Base& konvertiert werden kann. Macht aber auch nix, ich verwende dann halt die insert-Funktion:

    /*
     *   boost::ptr_map
     */
    typedef boost::ptr_map<std::string, Base> ptrMap;
    
    ptrMap map;
    map.insert(std::string("drucker"), new Drucker());
    map.insert(std::string("scanner"), new Scanner());
    //map["zweiterscanner"] = new Scanner(); // geht nicht: C2679
    
    ptrMap::iterator mit = map.find(std::string("drucker"));
    for(mit=map.begin();mit != map.end();mit++)
    {
    	mit->second->print();
    }
    map.clear();
    std::cout << map.size();
    

    Übrigens, wieso klappt folgendes nicht:

    typedef std::map<std::string, Base*> Map;
    typedef boost::shared_ptr<Base> BasePtr;
    typedef std::map<std::string, BasePtr> BMap;
    typedef boost::ptr_map<std::string, Base> ptrMap;
    
    Map map;
    map["blabla"] = new Scanner();
    
    BMap bmap;
    bmap["blub"] = new Drucker();
    
    ptrMap pmap;
    pmap.insert("scanner", new Scanner()); // const char[8] kann nicht zu std::string konvertiert werden...
    // oben gings doch auch :D
    

Log in to reply