Eigenes Objekt löschen



  • 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



  • Simon2 schrieb:

    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.

    Nur gibts meistens viel einfachere Lösungen für das eigentliche Problem. Liegt aber glaub ich auch an diesem Forum hier, da werden sehr oft Fragen zu den komischsten Implementierungsideen beantwortet, anstatt mal nach dem eigentlichen Problem zu fragen.



  • hmmmm? schrieb:

    ...Nur gibts meistens viel einfachere Lösungen für das eigentliche Problem. Liegt aber glaub ich auch an diesem Forum hier, da werden sehr oft Fragen zu den komischsten Implementierungsideen beantwortet, anstatt mal nach dem eigentlichen Problem zu fragen.

    😮 😮 😮
    Also dann hast Du diesen Thread (und viele andere, die ich kenne) nicht richtig gelesen.
    Der quillt doch über von "Gibt garantiert bessere Lösungen für ein konkretes Problem" (allein ich habe mich ja schon fast müde geschrieben daran).
    ... und das gilt für das Forum insgesamt: Eigentlich ist IMMER die erste Gegenfrage "Welches Problem willst Du eigentlich lösen?"
    Dein Einwand klingt eher nach Bashing ...

    Aaaber: Wie leicht aus dem Ausgangspost herauszulesen ist, geht es hier nicht um die Lösung eines bestimmten Problems, sondern um die Chancen und Risiken eines bestimmten (durch die Sprache erlaubten) Konstrukts.
    Das ist wie die Frage "Wie funktioniert eigentlich eine Einspritzpumpe am Auto?"
    Man muss ja keine Lust haben, sich mit dieser Frage zu beschäftigen, aber einfach zu sagen: "Wo willst Du denn hin?" - beantwortet weder die Frage, noch hilft sie sonst weiter.
    Natürlich kann man sagen: "Brauchst Du nicht zu wissen!" (was vielleicht sogar stimmt) ... aber das hat Neugierde zum Glück noch nie aufhalten oder abwerten können.

    Gruß,

    Simon2.



  • Ging jetzt auch nicht speziell um diesen Thread. Sicher gibts einige die erst mal nach dem eigentlichen Problem fragen, aber die die komplizierte Lösungen posten sind meisten mehr.



  • hmmmm? schrieb:

    Ging jetzt auch nicht speziell um diesen Thread. Sicher gibts einige die erst mal nach dem eigentlichen Problem fragen, aber die die komplizierte Lösungen posten sind meisten mehr.

    Es gibt halt viele Wege etwas zu lösen und das sieht man dann auch hier. Sei doch froh, dass sich viele hier die Mühe machen lange über ein Problem, Lösungen und dessen Folgen zu machen. Oftmals hilft das möglicherweise wirklich nicht direkt zu dem eigentlichem Problem, aber man kann andersweitig davon profitieren.



  • Ob man nun "delete this;" schreibt, oder "delete obj;" in einer Free-Function, ist doch im Endeffekt egal.
    Bei intrusive reference counting (wurde ja schon als Beispiel gebracht) setze ich persönlich z.B. grundsätzlich Memberfunktionen ein. Und das führt automatisch zu einem "delete this;".


Anmelden zum Antworten