"Content"-Verwalter mittels Template Spezialisierung - prinzipielles Design. Schön oder hässlich?



  • Nexus schrieb:

    Vielleicht mit std::function als Ladefunktion? Dann kannst du über std::bind() beliebige Parameter reinpacken.

    Übrigens gäbs für SFML-Ressourcen sowas Ähnliches schon fertig (Resource-Codebeispiel am Ende).

    Schamlose Werbung 😛

    Danke, aber deine Lösung ist ja genau das was ich vermeiden wollte (so hab ich es bisher selbst auch gemacht) - ich möchte kein einzelnes Objekt für jeden Verwalter eines bestimmten Resourcentyps haben.



  • Wie sieht denn der Code aus, der deine Load() -Funktion aufruft? Du kannst ja nicht nur wegen technischer Limitierungen (Template-Spezialisierung) keine Parameter hinzufügen, sondern weil du dann nicht weisst, was du beim Aufruf von Load() übergeben sollst.

    Daher auch der Vorschlag mit std::function . Das ist sowieso viel flexibler als ein String...



  • Der String ist ja immer gleich, das ist eine Ortsangabe für ne Datei. Das aufrufen sieht dann ungefähr so aus:

    void Gamestate::LoadContent(Content *content)
    {
        sf::Image *i = content.Load<sf::Image>("hier.jpg");
        Tilemap *map = content.Load<Tilemap>("dort.tilemap");
    }
    

    etc.

    Kannst du ein kurzes Beispiel geben, wie du es mit std::function und std::bind anwenden wuerdest? Ich verwende beide so gut wie nie.



  • Okay, ich ging von einem generischen Design aus, wo die Schnittstelle durch den Aufruf festgelegt ist:

    template <typename T>
    void LoadContent(Content* content, const std::string& where)
    {
       T* resource = content.Load<T>(where);
       // irgendwas Generisches hier
    }
    

    Hier hättest du dann halt Callbacks übergeben können, die den genauen Ladevorgang beschreiben (und evtl. weitere Parameter mit sich tragen).

    Aber wenn du Load() mit konkreten Typen als Template-Argument aufrufst, wieso brauchst du überhaupt ein Template? Was tut denn der Content ? Er hat ja keine ressourcen-spezifischen Informationen, da er selbst T nicht kennt. Warum nicht direkt freie Load() -Funktionen, und warum keine Überladung?



  • Da hast du allerdings Recht. Ich hatte aus irgendeinem Grund angenommen, dass ein static Objekt innerhalb der Funktion nur static fuer die Funktionsinstanz des dazugehoerigen Klassenobjekts ist. Das haette es leicht gemacht, für verschiedene Gamestates verschiedene Memory-Pools zu haben, die man ohne Probleme nach Bedarf löschen(speicher freigeben) und neu laden kann.

    Da muss ich wohl noch etwas nachdenken und einen Weg finden, diese static maps in die Objektinstanz auszulagern, ohne dabei die ganze Klasse auf den template-typ zu reduzieren.



  • templat0r schrieb:

    Da hast du allerdings Recht. Ich hatte aus irgendeinem Grund angenommen, dass ein static Objekt innerhalb der Funktion nur static fuer die Funktionsinstanz des dazugehoerigen Klassenobjekts ist.

    Das ist auch so, wenn ich dich richtig verstehe. Muss ja, wenn das static -Objekt z.B. den Typ T (oder davon abhängig) hat.

    Aber das erklärt immer noch nicht, warum du die Klasse brauchst, und keine freien Funktionen reichen. Bedenke dass du mit Funktionsüberladung das Gleiche wie mit Funktionstemplate-Spezialisierung erreichen kannst, wenn du einen Trick von Alexandrescu anwendest:

    template <typename T>
    struct Type {};
    
    // Überladung anhand des ersten Parameterypen
    Tilemap*   Load(Type<Tilemap> tag, const std::string& location);
    sf::Image* Load(Type<sf::Image> tag, const std::string& location, const sf::Color& colorKey = sf::Color::Magenta);
    


  • Nexus schrieb:

    templat0r schrieb:

    Da hast du allerdings Recht. Ich hatte aus irgendeinem Grund angenommen, dass ein static Objekt innerhalb der Funktion nur static fuer die Funktionsinstanz des dazugehoerigen Klassenobjekts ist.

    Das ist auch so, wenn ich dich richtig verstehe. Muss ja, wenn das static -Objekt z.B. den Typ T (oder davon abhängig) hat.

    Eben nicht. Ich führe es mal aus:

    class bar
    {
    	public:
    	void foo()
    	{
    
    		static int a=0;
    		a++;
    
    		std::cout<<a<<std::endl;
    	}
    };
    

    das static int a ist im Prinzip meine map. Nun ein Test:

    bar *meina = new bar();
    		meina->foo();
    		meina->foo();
    
    		delete meina;
    
    		bar meinb;
    		meinb.foo();
    

    Schade, gibt trotzdem 3 aus, obwohl es erst 1 2 und dann 1 ausgeben lassen sollte.

    Würde es so funktionieren, könnte ich es mir einfach zu nutze machen. Ein aktueller Spielzustand wird beendet, und ich weiss, dass ich seinen Speicher in absehbarer Zeit nicht brauchen werde (wenn man z.B. das Spiel gestartet hat und das Hauptmenü nicht mehr sehen wird, bis der Spieler explizit ESC-> zum Hauptmenü auswählt) und könnte einfach das in diesem Spielzustand gespeicherte lokale Content Objekt löschen, sodass alle darin enthaltenen static maps und ihre nicht-pointertypen, die sie speichern, freigegeben werden:

    [cpp]
    delete m_content;
    [cpp]

    Tada!

    Aber nun funktioniert das so nicht. Die static maps bleiben hartnÄckig, bis zum bitteren Ende im Speicher. Also muss ich einen Weg aussenrum finden, wenn ich mein bisheriges (wie ich finde) simples Interface beibehalten will.

    Wie deine vorgeschlagene Lösung mit den globalen Funktionen imr da weiterhilft, weiss ich ehrlich gesagt nicht 😛



  • Wenn du willst, dass eine Variable an die Lebenszeit eines Objekts gebunden ist, mach sie eben nicht statisch. Mit static sagst du ja gerade, dass sie unabhängig von den Objekten ist!

    Die freien Funktionen habe ich gepostet, weil ich nicht verstanden habe, wozu du eine Klasse benötigst. Verstehe ich übrigens immer noch nicht, vielleicht solltest du das erklären. Was genau enthält Content noch ausser dem Funktionstemplate?



  • Nexus schrieb:

    Wenn du willst, dass eine Variable an die Lebenszeit eines Objekts gebunden ist, mach sie eben nicht statisch. Mit static sagst du ja gerade, dass sie unabhängig von den Objekten ist!

    Ja, das weiss ich mittlerweile auch. Wie gesagt, da habe ich mich geirrt. Wie mache ich die maps einfach nicht-statisch, ohne dabei ein gesamtes Content Objekt auf einen bestimmten Typ zu reduzieren?

    Nexus schrieb:

    Die freien Funktionen habe ich gepostet, weil ich nicht verstanden habe, wozu du eine Klasse benötigst. Verstehe ich übrigens immer noch nicht, vielleicht solltest du das erklären. Was genau enthält Content noch ausser dem Funktionstemplate?

    Hab ich doch schon zig mal geschrieben!

    Die Lebzeiten der Resourcen wären an die Lebzeiten eines Content-Objektes gebunden, was de und neu allokieren von Speicher meiner Resourcen einfach machen würde. Dafür ist die Content Klasse da. Speichermanagagement.



  • Okay, dein Design hat mich wohl etwas verwirrt.

    templat0r schrieb:

    Wie mache ich die maps einfach nicht-statisch, ohne dabei ein gesamtes Content Objekt auf einen bestimmten Typ zu reduzieren?

    Wenn du das Verhalten an die Lebenszeit des Objekts binden möchtest, führt kein Weg an nicht-statischen Membervariablen vorbei.

    Allerdings möchtest du nicht, dass Content die konkreten Typen T kennt, daher musst du hier mit Type Erasure arbeiten. Das ginge z.B. über eine abstrakte Basisklasse, deren (templatisierte) Implementierungen dann die Löschung der Ressourcen durchführen würden. Aber da du das auch nicht willst, käme wieder std::function in Frage, bzw. ein Container davon.

    // Funktion für Freigabe
    template <typename T>
    void DeleteResource(T* resource)
    {
    	delete resource;
    }
    
    // In der Klasse
    std::vector< std::function<void()> > callbacks;
    
    // Registrierung einer Ressource zum Freigeben, in Load()
    Tilemap* map = new Tilemap;
    callbacks.push_back( std::bind(&DeleteResource<Tilemap>, map) );
    

    Im Destruktor der Klasse kannst du dann alle Callbacks aufrufen. Genaueres über std::function erfährst du auf der Dokumentation von Boost.Function (ist von Boost in den C++-Standard übernommen). Ansonsten gäbs auch eine TR1-Implementierung std::tr1::function .



  • Aha, und wie komme ich dann nach dem erstellen der Tilemap wieder an diese ran? Dass ich es löschen kann bringt mir ja nicht wirklich was.

    Der ganze Sinn des Konstrukts ist ja, dass der Benutzer der Lib dem Content Objekt sagen kann: "Hey, ich hätte gerne data/maps/tilemap1.map geladen", das Content Objekt schaut dann, ob das Teil schonmal geladen wurde und liefert in dem Fall nur noch die Referenz da drauf zurück, ohne gleich ein komplett neues zu erstellen.



  • Also, nur damit das nicht falsch rüberkommt: Löschen bringt mir schon was, das will ich ja auch können - aber das alleine bringt mir natürlich nichts 🙂 Denn Objekte anlegen und löschen könnte ich auch manuell mit new und delete am Ort wo ich sie brauche, dann müsste ich das Parsen halt in die Klasse Tilemap umlagern. Aber das will ich ja vermeiden.



  • Hallo, sorry für den Bump, aber ich hab die Lösung etwas umgeschustert. Wenn ich das machen will, was ich vorhabe, dann komm ich um void pointer nicht herum.

    Die Lösung ist soweit ich weiss relativ sicher, wenn was schief geht gibts null-pointer die man abfangen kann.

    Ich sollte das ganze wahrscheinlich eher mit Exceptions lösen, aber dazu bin ich erstmal zu faul, wenn ich ehrlich bin.

    Die Content Klasse sieht dabei so aus:

    class Content
    		{
    		public:
    			~Content()
    			{
    				for(auto it = m_resources.begin(); it!= m_resources.end(); ++it)
    				{
    					delete it->second.ptr;
    				}
    			}
    
    			template <class T>
    			T* Load(const std::string &location);
    
    		private:
    			template <class Resource>
    			void AllocateResource(const std::string &key, Resource *res)
    			{
    				ResourceInformation info (typeid(res), res);
    				m_resources.insert(std::make_pair(key,info));
    			}
    
    			template <class Resource>
    			Resource *SafeConversion(const ResourceInformation &i)
    			{
    				if(typeid(Resource*) == i.type_id)
    					return static_cast<Resource*>(i.ptr);
    				else
    					return 0;
    			}
    
    			std::map<std::string, ResourceInformation> m_resources;
    		};
    

    ResourceInformation ist dabei ein einfaches struct:

    struct ResourceInformation
    		{
    			ResourceInformation(const std::type_info &id, void* pointer)
    				: type_id(id), ptr(pointer)
    			{
    			}
    
    			ResourceInformation()
    				: type_id(typeid(int)), ptr(0)
    			{
    			}
    
    			const std::type_info &type_id;
    			void * ptr;
    		};
    

    Parser können (müssen) immernoch per Spezialisierung eingeführt werden:

    template<>
    		sf::Image* Content::Load(const std::string &location)
    		{
    			if(m_resources.find(location) == m_resources.end())
    			{
    				sf::Image *i = new sf::Image();
    				i->LoadFromFile(location);
    				i->CreateMaskFromColor(sf::Color::Magenta);
    				AllocateResource<sf::Image>(location,i);
    			}
    
    			return SafeConversion<sf::Image>(m_resources[location]);
    		}
    

    Somit kann ich nun die Lebenszeit von bestimmten Resourcen an einzelne Content-Objekte binden.



  • Ach ja, das mit den optionalen Parametern für die Funktion bekomme ich noch nicht hin. Dafür muss ich auf ne msvc Version warten, die variadic templates unterstützt. Mit denen geht's dann (mittels Spezialisierung) relativ einfach von der Hand.


Anmelden zum Antworten