Ressourcen-Manager



  • Hallo da!
    Ich würde in meinem Programm gerne verschiedene Ressourcen (Bilder und ähnliches) nur einmal laden. Darum würde ich gerne für die einzelnen Ressourcen-Typen "Manager" Klassen einführen, welche diese Objekte laden und einen smart-pointer (boost::shared_ptr) darauf liefern. Möchte nun ein zweiter dasselbe Objekt, dann schaut der Ressourcen-Manager nach, ob es schon geladen wurde und liefert stattdessen eine Kopie des alten Zeigers.
    So habe ich das nun auch implementiert, aber das Problem ist natürlich, dass der Manager ja selbst eine Referenz auf die Objekte speichern muss und sie daher nicht freigegeben werden, wenn der letzte Benutzer seine Referenz frei gibt, sondern erst, wenn der Manager zerstört wird.

    Auf Anhieb fällt mir jetzt nur ein, dass ich im Manager nur weak_ptr's (damit die Objekte zur rechten Zeit gelöscht werden) speichere und mir einen custom-deleter schreibe, der den jeweiligen weak_ptr aus dem Manager-Container löscht.

    Bevor ich das nun mache, wollte ich mal fragen, ob es da schon was in boost gibt oder eine elegantere Variante... Ich habe bis jetzt noch nichts in die Richtung gefunden.

    Viele Grüße,
    Michael



  • Warum eine Resource in einen shared_ptr kapseln wenn du sie eh zentral vom Manager verwalten lassen willst?



  • Sorry, ich kann Dir gerade nicht folgen 😕
    Das mit dem shared_ptr dient in meinem Fall nicht der RAII (oder zumindest nicht ausschließlich), sonder darum, dass der Manager feststellen kann, wieviele Benutzer einer Ressource es gibt.
    Also, das ganze soll so funtionieren:

    image_ptr a, b;
    image_weak_ptr c;
    
    a = mgr.load( "some.png" );
    b = mgr.load( "some.png" );
    
    assert( a==b );
    c = a;
    a.reset(); b.reset();
    assert( c.expired() );
    


  • Decimad schrieb:

    Sorry, ich kann Dir gerade nicht folgen 😕

    Du hast Resourcen. Die von einem Resourcen Manager verwaltet werden.
    Dein Problem ist nun, dass shared_ptr hier nicht wirklich funktionieren.

    einfach rohe Zeiger liefern und verlangen dass ReleaseResource() aufgerufen wird und selber ref counten

    das ganze ist notfalls auch in eine eigene smart ptr klasse kapselbar - aber rohe zeiger gefallen mir hier mehr.

    wenn es unbedingt shared_ptr sein müssen. einfach intern keinen shared_ptr halten sondern nur einen rohen zeiger und einen custom deleter verwenden der dann eben release() aufruft...

    PS:
    du willst nur refcounten? Warum zählst du dann nicht die anzahl der acquireResource() aufrufe...?



  • Nein, ich möchte möglichst ohne Codewände erreichen, dass der Manager mitbekommt, wenn er selber der einzige ist, der noch eine Referenz zu der Ressource hält (und diese dann freigeben kann, oder zumindest seine Referenz im assoziativen Container löschen).



  • Decimad schrieb:

    Nein, ich möchte möglichst ohne Codewände erreichen, dass der Manager mitbekommt, wenn er selber der einzige ist, der noch eine Referenz zu der Ressource hält (und diese dann freigeben kann, oder zumindest seine Referenz im assoziativen Container löschen).

    shared_ptr mit custom deleter und der manager hält selber keinen shared_ptr sondern nur einen rohen. so wird der deleter aufgerufen wenn der manager die letzte referenz hält.



  • Genau diese Lösung (mit weak_ptr anstatt normalen Zeiger) habe ich doch in meinem Eingangsposting beschrieben und nur um evtl. vorgefertigte oder elegantere Lösungen gebeten, weil ich mir dachte, dass man evtl. öfter auf dieses Problem stößt.

    Viele Grüße,
    Michael



  • Mir gefällt dieses viele sinnlose ref counten halt nicht.

    ich würde es ohne smart pointer machen - jedes acquire() verlangt ein release().

    aber wenn du eben garbage collection willst, dann ist es wohl am sinnvollsten über den custom deleter. es wirkt aber halt sehr java like...



  • acquire() und release() stellen doch auch nur ein manuelles ref-counten dar. Und ich dachte, dass man in C++ sowas kapselt, dass man keinen Fehler machen kann usw. Das eigentliche Problem ist, dass sowas immer derart ausufern muss, mit Quellcodewänden für die einfachsten Probleme.

    Mein Konzept sieht jetzt erstmal so aus, das ist doch eine Qual!

    #include <iostream>
    #include <string>
    #include <map>
    #include <boost/shared_ptr.hpp>
    #include <boost/weak_ptr.hpp>
    
    template<typename container_type>
    class erase_deleter {
    public:
    	erase_deleter( container_type& cont, typename container_type::iterator it, boost::shared_ptr<bool> alive ) 
    		: cont_(cont), it_(it), alive_(alive) {}
    
    	erase_deleter( const erase_deleter& d ) 
    		: cont_(d.cont_), it_(d.it_), alive_(d.alive_) {}
    
    	template<typename T>
    	void operator()( T* p ) {
    		if( *alive_ ) {
    			std::cout << "erasing from map\n";
    			cont_.erase( it_ );
    		}
    		delete p;
    	}
    
    	typename container_type::iterator it_;
    	container_type& cont_;
    	boost::shared_ptr< bool > alive_;
    };
    
    class myobject {
    public:
    	typedef boost::shared_ptr< myobject > shared_ptr;
    
    	myobject( const std::string& str ) : str_(str)
    	{
    		std::cout << str_ << " constructed.\n";
    	}
    
    	~myobject() {
    		std::cout << str_ << " destroyed.\n";
    	}
    
    private:
    	std::string str_;
    };
    
    class mgr
    {
    private:
    	typedef std::map< std::string, boost::weak_ptr<myobject> > container_type;
    
    public:
    	mgr() : alive_( new bool(true) ) {}
    	~mgr() { *alive_ = false; }
    
    public:
    	boost::shared_ptr< myobject > get( const std::string& name ) {
    		container_type::iterator it = objects_.find( name );
    		if( it == objects_.end() ) {
    			it = objects_.insert( std::make_pair( name, boost::weak_ptr<myobject>() ) ).first;
    			boost::shared_ptr< myobject > ptr( new myobject( name ), erase_deleter< container_type >( objects_, it, alive_ ) );
    			it->second = ptr;
    			return ptr;
    		} else {
    			return it->second.lock();
    		}
    	}
    public:
    //private:
    	container_type objects_;
    	boost::shared_ptr< bool > alive_;
    };
    
    int main(int argc, char* argv[])
    {
    	mgr *a = new mgr();
    
    	myobject::shared_ptr p1 = a->get( "test.png" );
    	myobject::shared_ptr p2 = a->get( "test.png" );
    
    	delete a;
    
    	std::cout << "resetting p1\n";
    	p1.reset();
    	std::cout << "p1 resetted\n";
    
    	std::cout << "resetting p2\n";
    	p2.reset();
    	std::cout << "p2 resetted\n";
    
    	std::cout << "finished\n";
    
    	return 0;
    }
    


  • Decimad schrieb:

    acquire() und release() stellen doch auch nur ein manuelles ref-counten dar.

    Man kann es automatisieren.

    Und ich dachte, dass man in C++ sowas kapselt, dass man keinen Fehler machen kann usw.

    Auf jedes Problem refcounting zu werfen ist auch nicht wirklich die ideale kapselung 😉

    Der Code aber ansich ist ganz OK. Der Client Code ist richtig schön. Und wenn du eine ptr_map statt diesem weak_ptr zeugs nimmst, wird auch das backend schöner.

    Dann hast du nur das Problem dass dein Manager vor den Resourcen sterben kann. Doofe Sache dass. Gefällt mir nicht wirklich.

    Es ist einfach ineffizient einen GC über refcount zu implementieren. Ich denke Java oder C# wären mit dem Ansatz sehr schön, aber in C++ will ich automatische Objekte haben - will RAII nutzen, etc.



  • Für diesen Fall (mgr stirbt vor den referenzen) ist ja dieser dämliche bool-pointer implementiert (würg!). Wenn der Code nicht einfach so total hässlich wäre, würde ich ihn direkt einsetzen (das mit der ptr_map schau ich mir gerade mal an!), aber ich schrecke noch etwas zurück! Vielleicht wirds ja angenehmer, wenn ich die deklaration von der implementation trenne und in einen impl-Header auslagere^^

    Viele Grüße,
    Michael



  • Decimad schrieb:

    Für diesen Fall (mgr stirbt vor den referenzen) ist ja dieser dämliche bool-pointer implementiert (würg!).

    Eben. Und das verstehe ich gerade nicht.
    Warum kann der Manager vor den Resourcen sterben?

    Wenn du das wegdesignen kannst, wird der code wieder um ein eck schöner.



  • Naja, also, hrmm, im Normalfall sollte das nicht passieren (also in dem Programm, zu dessen Teil das hier werden soll), weil dort nur ein einziges globales Objekt vorherrscht und der Rest vor diesem zerstört wird (MFC... aber ich codiere sowieso irgendwie daran vorbei, weil ich mir von MFC nicht vorschreiben lassen will, wie ich etwas anzugehen habe^^). Aber irgendwie kam ich nicht umhin, eine "Was wäre wenn?"-Implementation zu schreiben 🙂
    Das mit der ptr_map wird so leider nichts, weil ich aus normalen Zeigern keine shared_ptr generieren kann... Ich könnte allenfalls auf intrusive_ptr umsteigen, aber dann müsste ich die Ressourcen-Zeiger wieder wrappen 😞

    Viele Grüße,
    Michael



  • Decimad schrieb:

    Das mit der ptr_map wird so leider nichts, weil ich aus normalen Zeigern keine shared_ptr generieren kann...

    Also unabhängig vom Sinn oder Unsinn des ganzen (dazu habe ich mir den Thread einfach nicht komplett durchgelesenen): Was ist mit enable_shared_from_this?



  • Das benutze ich auch oft, allerdings kommt es hier in nicht frage, weil ich dazu die gespeicherten Typen wrappen müsste.

    Danke dennoch für den Vorschlag!

    Viele Grüße,
    Michael


Log in to reply