Eigenes Objekt löschen



  • Was würdet ihr von folgender Destroy-Methode halten und sagen ? :

    class Klasse
    {
        Klasse();
        void Methode1();
        void Methode2();
    
        void Destroy()
        {
            delete this;
        }
    };
    

    Ich benutze sowas nicht, aber ich möchte eure Meinung dazu wissen.



  • Ist das eigentlich eine spezialität von C++ Programmieren, dass sie alles mögliche was man an komischem Code schreiben kann, auch machen



  • FreakY<3Cpp schrieb:

    Was würdet ihr von folgender Destroy-Methode halten und sagen ?

    Es gibt Situationen wo das sinnvoll sein kann - aber die meisten Menschen werden nie in so eine Situation kommen...



  • hmmmm? schrieb:

    Ist das eigentlich eine spezialität von C++ Programmieren, dass sie alles mögliche was man an komischem Code schreiben kann, auch machen

    Ich glaube, es ist eine Spezialität von C++-Programmierern, dass sie sich gerne mit den Möglichkeiten und Grenzen der Sprache machen und gerne Neues ausprobieren.
    Wem es reicht, jeweils einen Standardweg für Standardprobleme zu haben, sollte eine andere Sprache (deren Namen ich jetzt hier nicht nennen möchte 😉 ) nehmen. 😃

    @Freaky: Ich glaube, der Code ist nicht in jeder Situation gefährlich ... aber in fast jeder. 😉
    Beispiele:
    - Woher soll der "Besitzer" des Objekts wissen, dass sein Objekt nicht mehr gültig ist (Kann zwar auch auf Anderem Weg passieren, aber hier ist es wirklich eingebaut).
    - Was mit Objekten, die gar nicht mit new erzeugt wurden?
    - Sobald "Destroy()" mehr macht als nur delete: Das "klassische" Zerstören von außen (Scope/delete) ruft Destroy() ja nicht auf ... da fehlt diese Funktionialität also.

    Insgesamt: Kann man machen, braucht aber sehr viele flankierende Maßnahmen und größte Vorsicht - und das bei eher geringem Nutzen.
    Ich würde meine Objekte immer so zu stricken versuchen, dass man sie mit keiner Aktion "lebensunwert" machen kann, so dass es reicht, sie jeweils von außen zu zerstören.

    Gruß,

    Simon2.



  • Ich finde delete this in mehreren Situationen praktisch und setze dies auch ein.
    Noch zwei Kommentare:

    - Was mit Objekten, die gar nicht mit new erzeugt wurden?

    ctor / dtor private. Eine (statische) make(..) Methode die eine Heap basiertes Objekt zurückgibt und eben eine destroy() Methode, welche delete this aufruft.

    So wird erzwungen, dass das Objekt auf dem Heap ist, was manchmal nötig ist.

    - Woher soll der "Besitzer" des Objekts wissen, dass sein Objekt nicht mehr gültig ist (Kann zwar auch auf Anderem Weg passieren, aber hier ist es wirklich eingebaut).

    Es ist nicht mehr gültig, weil der Besitzer eben Destroy() aufgerufen hat. Genau wie nach einem delete. Sehe da keinen Unterschied.

    Simon



  • theta schrieb:

    Ich finde delete this in mehreren Situationen praktisch und setze dies auch ein.

    das würde mich echt mal interessieren.



  • Die erste Situation ist oben schon genannt. Ein Objekt macht nur auf dem Heap Sinn (weil es z.B. via Windows Messages gepostet wird) und muss desshalb mit new erzeugt sein.

    Eine andere Situation ist, wenn ein Objekt über DLL Grenzen hinweg erzeugt wird (heisst new XXX ist im Code der DLL). Dann muss auch das delete wieder im DLL Code sein (z.B. weil verschiedene Runtimes benutzt werden). Dies kann einfach realisiert werden indem die entspechenden Objekte eine destroy() Methode haben.

    Momentan fallen mir nur diese beiden Situationen ein.
    An welche hast Du gedacht?

    Simon



  • theta schrieb:

    ...

    - Was mit Objekten, die gar nicht mit new erzeugt wurden?

    ctor / dtor private. ...

    Das ist mir schon klar. Aber das reicht noch nicht - man muss sich auch mit Kopieren, Referenzieren, ... beschäftigen.
    Das sind die Dinge, die ich meinte mit

    Simon2 schrieb:

    ...
    Insgesamt: Kann man machen, braucht aber sehr viele flankierende Maßnahmen und größte Vorsicht - und das bei eher geringem Nutzen.
    ...

    In meinen Augen sind die von Dir genannten Beispiele ("macht nur auf Heap Sinn", Erzeuger/Vernichter-Trennung) nicht Eigenschaften eines Objekts, sondern seiner Umgebung ... und großartige Einsatzgebiete für Factory- bzw. Managerklassen.
    Ich sehe da weder die Notwendigkeit noch nennenswerte Vorteile ggü. den Bordmitteln "Konstruktor/Destruktor".

    Nochmal: Man kann sowas bestimmt vorteilhaft einsetzen ... aber die beiden Beispiel zählen für mich nicht dazu.

    Noch ein paar Anmerkungen von mir:
    1.)

    theta schrieb:

    ...Es ist nicht mehr gültig, weil der Besitzer eben Destroy() aufgerufen hat. Genau wie nach einem delete. Sehe da keinen Unterschied....

    Der Unterschied ist, dass delete ein Standardmechanismus ist, dessen Verhalten standardisiert ist. Schonmal daran gedacht, dass mit "Destroy()" kein smartpointer o.a. "Tools" genutzt werden kann? "delete" kennen die alle, aber für "Destroy"-Objekte musst Du alles neu erfinden.

    2.)
    Ich würde sehr genau überlegen, wie sich "interner Status" (wie z.B. "ungültig") und "C++-Lebenszeit" miteinander zusammenhängen und nicht allzu schnell das eine mit dem Anderen verbinden. Die std::-Streams sind da ein gutes Beispiel ...

    Gruß,

    Simon2.



  • theta schrieb:

    Die erste Situation ist oben schon genannt. Ein Objekt macht nur auf dem Heap Sinn (weil es z.B. via Windows Messages gepostet wird) und muss desshalb mit new erzeugt sein.

    dh ich darf kein fast_alloc verwenden weil ich viele Messages schicke?
    ja, macht sinn...

    Eine andere Situation ist, wenn ein Objekt über DLL Grenzen hinweg erzeugt wird (heisst new XXX ist im Code der DLL). Dann muss auch das delete wieder im DLL Code sein (z.B. weil verschiedene Runtimes benutzt werden). Dies kann einfach realisiert werden indem die entspechenden Objekte eine destroy() Methode haben.

    oder noch besser. ich erstelle objekte per

    make_xxx()
    und zerstöre sie per
    destroy_xxx()

    wäre viel konsistenter und man könnte leichter mehrere alloc strategien anbieten.

    An welche hast Du gedacht?

    dirty hacks die man vor dem anwender verstecken will. wie gesagt: die wenigstens leute weren je in eine situation kommen wo ein delete this sinn macht.

    deine beispiele haben sowieso eine create_xxx funktion - das schreit doch nach eienr delete_xxx funktion...



  • Kann man auch irgendwie für abgeleitete Klassen von "Klasse" mit protected Destruktor erzwingen, dass sie auf dem Heap erstellt werden ohne was an der abgeleiteten Klasse zu ändern(also Destruktor dort ebenfalls private bzw. protected zu machen)?



  • Ich habe hin und wieder referenzgezählte Objekte. Das Modul bekommt von irgendwo her ein Objekt und ruft addRef auf, um die Referenz hoch zu zählen. Ist das Modul mit dem Objekt fertig, teilt es das mit release dem Objekt mit. Da das Objekt seinen Refernzzähler kennt, kann es dann selbst erkennen, ob jemand noch eine Referenz auf das Objekt hält. Wenn nicht, dann ruft das Objekt eben dieses berühmte "delete this" auf.



  • könnte das zu einer Endlosschleife führen?

    Klasse::Klasse() { delete this; } 
        Klasse::~Klasse() { new this; }
    


  • xBlackKnightx schrieb:

    könnte das zu einer Endlosschleife führen?

    nein, nur zu compilerfehlern.



  • FreakY<3Cpp schrieb:

    Was würdet ihr von folgender Destroy-Methode halten und sagen ? [...]
    Ich benutze sowas nicht, aber ich möchte eure Meinung dazu wissen.

    Ich finde sowas sehr schlecht, da unterschiedliche Kontrollebenen für Speicheranforderung und -freigabe erforderlich sind.

    Wenn man in C++ ein new einsetzt, erwartet man normalerweise, dass man ein Objekt mit delete wieder freigeben kann (von speziellen Anwendungsfällen wie Smart Pointers oder Pointer-Container mal abgesehen, bei denen man aber weiss, dass man sich nicht um die Freigabe zu kümmern hat). Ich finde auch C++-Bibliotheken sehr schlimm, die auf diese Weise versuchen, einen Garbage Collector nachzubauen und "benutzerfreundlicher" zu sein. Meistens resultiert das nur in Inkonsistenzen, unnötigem Overhead und Einschränkungen für den Benutzer.

    Zu den weiteren Sicherheits- und Fehleranfälligkeitsaspekten wurde ja bereits genügend gesagt...



  • Nexus schrieb:

    FreakY<3Cpp schrieb:

    Was würdet ihr von folgender Destroy-Methode halten und sagen ? [...]
    Ich benutze sowas nicht, aber ich möchte eure Meinung dazu wissen.

    Ich finde sowas sehr schlecht, da unterschiedliche Kontrollebenen für Speicheranforderung und -freigabe erforderlich sind.

    Wenn man in C++ ein new einsetzt, erwartet man normalerweise, dass man ein Objekt mit delete wieder freigeben kann (von speziellen Anwendungsfällen wie Smart Pointers oder Pointer-Container mal abgesehen, bei denen man aber weiss, dass man sich nicht um die Freigabe zu kümmern hat). Ich finde auch C++-Bibliotheken sehr schlimm, die auf diese Weise versuchen, einen Garbage Collector nachzubauen und "benutzerfreundlicher" zu sein. Meistens resultiert das nur in Inkonsistenzen, unnötigem Overhead und Einschränkungen für den Benutzer.

    Zu den weiteren Sicherheits- und Fehleranfälligkeitsaspekten wurde ja bereits genügend gesagt...

    Und wenn man ein MyObject* MyObject::create() einsetzt kommt ein MyObject->destroy() gar nicht so unerwartet und erfordert eben keine unterschiedlichen Kontrollebenen.

    Und natürlich sind da viele Spezialfälle, die solche Techniken erforderlich machen, aber das ist ja das schöne an C++: dass es eben auch für Spezialfälle geeignete Sprachmittel gibt und nicht nur für Standardfälle.



  • tntnet schrieb:

    Und wenn man ein MyObject* MyObject::create() einsetzt kommt ein MyObject->destroy() gar nicht so unerwartet und erfordert eben keine unterschiedlichen Kontrollebenen.

    Eine statische Factory-Methode? Also sowas?

    MyClass* ptr = MyClass::create();
    

    Aber für die Zerstörung gibt es keine analoge Methode, sondern das muss das Objekt selber übernehmen?

    ptr->Destroy();
    

    Das sieht mir nicht gerade nach gleicher Kontrollebene aus, geschweige denn würde ich sowas als "erwartet" bezeichnen.

    tntnet schrieb:

    Und natürlich sind da viele Spezialfälle, die solche Techniken erforderlich machen, aber das ist ja das schöne an C++: dass es eben auch für Spezialfälle geeignete Sprachmittel gibt und nicht nur für Standardfälle.

    Meiner Ansicht resultieren aber viele dieser Spezialfälle aus fehlerhaftem Design. In den meisten Fällen sollte es eigentlich möglich sein, ein Objekt konsistent zu erzeugen und zu zerstören. Zumindest hatte ich selber noch nie einen Fall, bei dem ich auf ein übles delete this zurückgreifen musste, und kann mir das deshalb vielleicht auch zu wenig vorstellen.



  • Ich fand sowas ganz hilfreich im Umfeld von Multithreading-Anwendungen... der Worker-Thread ist in einem Objekt gekapselt, und sobald er seinen Job erledigt hat löscht er sich höflicherweise selbst einschließlich seines Datenobjekts.

    Ansonsten muß man immer noch einen "Anker" haben, der ruhende Threads entfernt. Das schafft nur überflüssige Synchronisationsaufwände.


  • Administrator

    @Marc++us,
    Wäre bei sowas nicht ein Threadpool besser geeignet? Sobald ein Workerthread seine Arbeit beendet hat, wird er nicht zerstört, sondern legt sich schlafen, bis der Pool, zu welchem der Thread gehört, ihm eine neue Aufgabe zuteilt und wieder weckt.

    Für das erstellen und löschen von Threads ist dann die Klasse zuständig, welche den Pool verwaltet.

    Grüssli



  • Dravere schrieb:

    @Marc++us,
    Wäre bei sowas nicht ein Threadpool besser geeignet? Sobald ein Workerthread seine Arbeit beendet hat, wird er nicht zerstört, sondern legt sich schlafen, bis der Pool, zu welchem der Thread gehört, ihm eine neue Aufgabe zuteilt und wieder weckt.
    Für das erstellen und löschen von Threads ist dann die Klasse zuständig, welche den Pool verwaltet.
    Grüssli

    das ändert nix. dann ist es eben das Job-Objekt, das sich nach erledigung des jobs selber löscht (und dabei den zwischenzeitlich an sich gebundenen thread aus dem pool freigibt).


  • Administrator

    volkard schrieb:

    das ändert nix. dann ist es eben das Job-Objekt, das sich nach erledigung des jobs selber löscht (und dabei den zwischenzeitlich an sich gebundenen thread aus dem pool freigibt).

    So war das nicht gemeint. Zum einen soll der Job nichts weiteres sein als eine Art von Funktionszeiger, bzw. zum Beispiel ein boost::function<void()> , zum anderen soll der Thread nicht gelöscht werden, sondern eben schlafen gelegt. Das Objekt bleibt somit bestehen, der Thread wird nur über ein Event oder Mutex schlafen gelegt.

    Kurze, schnelle und womglich hässliche Implementierung, nur zur Verdeutlichung der Idee:

    #include <boost/thread.hpp>
    #include <boost/scoped_ptr.hpp>
    #include <boost/noncopyable.hpp>
    #include <boost/ptr_container/ptr_vector.hpp>
    
    #include <vector>
    #include <cassert>
    #include <algorithm>
    #include <functional>
    
    class WorkerThread
    	: public boost::noncopyable
    {
    	// Attributes //
    private:
    	bool m_continue;
    	boost::function<void()> m_job;
    
    	boost::mutex m_condition;
    	boost::scoped_ptr<boost::thread> m_thread;
    
    	// Constructor //
    public:
    	WorkerThread()
    		: m_continue(true)
    		, m_job()
    		, m_condition()
    		, m_thread()
    	{
    		m_condition.lock();
    		m_thread.reset(new boost::thread(&WorkerThread::run, this));
    	}
    
    	WorkerThread(boost::function<void()> job)
    		: m_continue(true)
    		, m_job(job)
    		, m_condition()
    		, m_thread()
    	{
    		m_condition.lock();
    		m_thread.reset(new boost::thread(&WorkerThread::run, this));
    	}
    
    	// Methods //
    private:
    	bool wait_on_job()
    	{
    		while(m_continue && !m_job)
    		{
    			m_condition.lock();
    		}
    
    		return m_continue;
    	}
    
    	void run()
    	{
    		boost::mutex mutex;
    		boost::unique_lock<boost::mutex> lock(mutex);
    
    		while(wait_on_job())
    		{
    			m_job();
    			m_job.clear();
    		}
    	}
    
    public:
    	bool is_free() const
    	{
    		return m_job.empty();
    	}
    
    	void stop()
    	{
    		m_continue = false;
    		m_condition.unlock();
    	}
    
    	void set_job(boost::function<void()> job)
    	{
    		assert(!job.empty());
    
    		m_job = job;
    		m_condition.unlock();
    	}
    
    	void join()
    	{
    		m_thread->join();
    	}
    };
    
    class WorkerThreadPool
    	: public boost::noncopyable
    {
    	// Typedefs //
    private:
    	typedef boost::ptr_vector<WorkerThread> Pool;
    
    	// Attributes //
    private:
    	 Pool m_pool;
    
    	// Constructor //
    public:
    	WorkerThreadPool()
    		: m_pool()
    	{
    	}
    
    	~WorkerThreadPool()
    	{
    		std::for_each(
    			m_pool.begin(),
    			m_pool.end(),
    			std::mem_fun_ref(&WorkerThread::join));
    	}
    
    	// Methods //
    private:
    	WorkerThread& get_worker()
    	{
    		Pool::iterator iter = m_pool.begin();
    		Pool::iterator end = m_pool.end();
    
    		for(; iter != end; ++iter)
    		{
    			if(iter->is_free())
    			{
    				return *iter;
    			}
    		}
    
    		m_pool.push_back(new WorkerThread());
    		return m_pool.back();
    	}
    
    public:
    	void execute(boost::function<void()> job)
    	{
    		WorkerThread& worker = get_worker();
    		worker.set_job(job);
    	}
    };
    

    So ist die Speicherverwaltung geregelt und es müssen nicht immer neue Threads erstellt werden, dadurch werden auch kleinere Aufgaben interessant, um sie einem Thread zu übergeben.

    Grüssli


Anmelden zum Antworten