Eigene Speicherverwaltung für Objekte auf dem Heap - Eure Meinung



  • Hi,

    oft muss ich in meinen Programmen Zeiger auf Objekte zurück geben. Ich bin also gezwungen das Objekt auf dem Heap zu erstellen. Jedenfalls wollte ich mich nicht immer um das freigeben per delete kümmern (wurde delete schon aufgerufen bla bla bla). Ich habe mir mal folgendes Konzept überlegt und wollte einfach mal eure Meinung dazu hören:
    Ich habe eine Basisklasse CBase. Alle Klassen die meine Speicherverwaltung nutzen, müssen von dieser Klasse abgeleitet werden.
    1. Es gibt ein globales, statisches, dynamisches Array dass als Heap-Liste dient... Es speichert jedes Objekt das auf dem Heap liegt.

    static std::set<void *> HeapObjects;
    

    2. CBase überläd den new operator. Der Zeiger der innerhalb der Operatorfunktion erstellt wird wird in einem dynamischen Array abgelegt.
    3. CBase hat eine Funktion SafeDelete() die, falls sie bei einem Stackobjekt aufgerufen wird, nichts tut, bei einem Heap Objekt sich selbst zerstört.
    4. CBase ist bei mir in einer DLL. Wird die DLL also freigegeben, so wird für alle Objekte die noch in der Heap-Liste sind SafeDelete() aufgerufen.

    So nun erstmal die Klasse:

    class CBase
    {
    public:
    	virtual ~CBase() //Der Destruktor soll überschreibbar sein... jede Klasse kann so ihre eigenen Aufräumarbeiten machen
    	{
    		this->Release(); //Aufruf der Speicherfreigabe
    	}
    
    	static void *operator new(size_t size)
    	{
    		void *pMemBlock;
    
    		pMemBlock = malloc(size);
    		HeapObjects.insert(pMemBlock); //Hinzufügen des Pointers zur Heap-Liste
    
    		return pMemBlock;
    	}
    
    	static void operator delete(void *ptr)
    	{
    		free(ptr);
    	}
    
    	virtual void Release() //Diese Funktion soll als interne Speicherfreigabefunktion dienen...
    	{
    		//Jede Klasse muss also ihren benutzen Speicher verwalten und hier frei geben (deshalb selbstverständlich virtual)... CBase soll nicht dafür zuständig
    		//sein um bei mehrfachen Release() aufrufen, zu prüfen ob der Speicher schon freigegeben wurde
    		//Dies soll die Klasse selber tun
    	}
    
    	void SafeDelete() //Diese Funktion soll seinen Zeiger aus der Heap-Liste löschen und nur den Klassenzeiger löschen
    	{
    		if(HeapObjects.find(this) != HeapObjects.end()) //Gelöscht wird nur wenn this in der Heap-Liste ist (also per new erstellt wurde). Objekte auf dem Stack haben also keinen Schaden falls SafeDelete() aufgerufen wird
    		{
    			this->Release(); //Erstmal den gesamten Speicher den die Klasse benutzt freigeben
    			HeapObjects.erase(this); //Zeiger aus der Liste austragen
    			delete this; //Speicher freigeben
    		}
    	}
    };
    

    Zu 4.

    while(HeapObjects.size())
    {
    	CBase *pObject = (CBase *)*HeapObjects.begin();
    
    	pObject->SafeDelete();
    	HeapObjects.erase(pObject);
    }
    

    Und mal eine Beispielklasse die CBase nutzt:

    class Sample : public CBase
    {
    private:
    	char *ptr;
    	int i;
    public:
    	Sample(bool b)
    	{
    		this->ptr = NULL;
    		this->i = 100;
    
    		if(b) //Das hier ist nur um die Release() Methode richtig zu demonstrieren
    		{
    			this->ptr = (char *)malloc(200);
    		}
    	}
    
    	void hi()
    	{
    		cout << "Hi" << this->i << endl;
    	}
    
    	void Release()
    	{
    		if(this->ptr) //Egal wie oft Release() aufgerufen wird... der Speicher wird so nur einmal freigegeben
    		{
    			free(this->ptr);
    			this->ptr = NULL;
    		}
    	}
    };
    
    //...
    
    Sample *p = new Sample(true);
    	p->SafeDelete();
    	p->hi();
    

    Probleme:
    -Das erste tritt schon im Beispiel auf. p->SafeDelete() löscht das Objekt vom Heap... danach wird p->hi() aufgerufen, was natürlich scheiße ist.

    So, das isses erstmal. Ich weiß da is jetzt null Fehlerüberprüfung drin und natürlich muss für new[] noch das selbe wie für new gelten aber das ganze ist erstmal nur ein Konzept.
    Kritiken sind sehr erwünscht 🙂

    Mfg, Keep Coding 😉



  • "safe delete" verheisst nie was Gutes 🤡

    Dein Ansatz erinnert mich an den Nachbau eines Garbage Collectors, wie man es z.B. in antiken GUI-Frameworks gemacht hat. Das "C"-Präfix ist auch schon länger nicht mehr in Mode. Mit void* kannst du auch nicht mehr typsicher löschen. delete this ist ab und zu ein Anzeichen von schlechtem Design und fehlender Symmetrie (freigeben soll der Besitzer des Speichers – irgendein kapselndes Objekt, aber normalerweise nicht das Objekt selbst).

    Das würde ich wirklich nicht machen, damit verwirrst du jeden C++-Programmierer. In modernem C++ benutzt man RAII – ist sehr einfach zu implementieren, aber wahnsinnig effektiv. Warum gibst du nicht einfach Smart-Pointer zurück? Am besten std::unique_ptr , sonst std::auto_ptr oder bei geteiltem Ownership std::shared_ptr .



  • hi nexus,

    das "C"-Präfix benutze ich ganz gerne weil ich für per Autovervollständigung nur "C" eingeben muss und dann kommen gleich alle Klassennamen... ich muss also nicht nochmal extra nachgucken wie die Klasse nun genau heißt.
    delete wird ja nie für einen void* angewendet. Das delete wird ja immer in SafeDelete() aufgerufen und da ist this ein CBase . Die Zeiger werden halt nur als void abgespeichert aber ich könnte sie genauso gut als CBase * speichern.

    Ja das mit dem delete this finde ich auch nicht perfekt. Da muss irgendwie noch ne bessere Lösung her. Smart-Pointer sind für mich keine Option da ihr Destruktor immer delete benutzt. Ich habe z.B. eine Funktion die prüft ob ein bestimmtes Objekt schon vorhanden ist, falls ja ein Zeiger darauf zurück gibt, falls nein ein neues Objekt mit new erstellt. Das Problem ist aber dass mindestens ein Objekt auf dem Stack liegt. Das ist eigentlich auch der Grund warum ich angefangen hab die Klasse zu schreiben.



  • 1700 schrieb:

    Smart-Pointer sind für mich keine Option da ihr Destruktor immer delete benutzt

    Das ist nicht ganz richtig.

    http://www.boost.org/doc/libs/1_47_0/libs/smart_ptr/shared_ptr.htm schrieb:

    template<class Y, class D> shared_ptr(Y * p, D d);
    template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);

    Requirements: p must be convertible to T *. D must be CopyConstructible. The copy constructor and destructor of D must not throw. The expression d(p) must be well-formed, must not invoke undefined behavior, and must not throw exceptions. A must be an Allocator, as described in section 20.1.5 (Allocator requirements) of the C++ Standard.

    Effects: Constructs a shared_ptr that owns the pointer p and the deleter d. The second constructor allocates memory using a copy of a.

    Postconditions: use_count() == 1 && get() == p.

    Throws: std::bad_alloc, or an implementation-defined exception when a resource other than memory could not be obtained.

    Exception safety: If an exception is thrown, d(p) is called.

    Notes: When the the time comes to delete the object pointed to by p, the stored copy of d is invoked with the stored copy of p as an argument.



  • gut das gilt für die boost smart pointer aber nich für die in der standard library



  • 1700 schrieb:

    Die Zeiger werden halt nur als void* abgespeichert aber ich könnte sie genauso gut als CBase * speichern.

    Warum tust du es dann nicht? Casten musst du so oder so, mit CBase* wäre wenigstens so viel Code wie möglich typsicher.

    1700 schrieb:

    gut das gilt für die boost smart pointer aber nich für die in der standard library

    std::shared_ptr und std::unique_ptr sind in aktuellen Standardbibliotheks-Implementierungen vorhanden, bei beiden kannst du benutzerdefinierte Deleter angeben. Ausserdem gibts std::tr1::shared_ptr schon seit 6 Jahren.

    Glaub mir, was du versuchst, ist reinstes Gefrickel. Du hast nach wie vor alle Nachteile der manuellen Speicherverwaltung, wenn du SafeDelete() explizit aufrufen musst. Zum Beispiel hast du keine Exceptionsicherheit, der Code wird komplexer, mehrere Rückgabewerte sind eine Fehlerquelle, der Aufrufer muss sich um Dinge kümmern, die nicht in seiner Verantwortung liegen, und so weiter...

    Alles Probleme, die man in sauberem C++ nicht hat. Schau dir einfach mal das RAII-Idiom an.


Anmelden zum Antworten