Designfrage: Mehrfachvererbung / Interfaces
-
Was aber aufs selbe hinausläuft wie beim intrusiven. Dort erstellst du ein T mit Args..., hier ists eben ein shared_ptr<T> mit Args....
-
Beim intrusiven schreibt mir niemand vor wie ich das Objekt zu erzeugen habe...
-
Du kannst meinen shared_ptr natürlich auch noch mit einem Allokator würzen, war ja nur skizziert.
-
Lassen wir das und kehren wir vielleicht mal zur ursprünglichen Frage zurück:
dot schrieb:
Baldur schrieb:
Wenn ich zum Beispiel nun in meinem Spiel ein Dialogfeld anzeige, kann man natürlich sagen, das Dialogfeld gehört dem Application Objekt und soll von diesem gelöscht werden, wenn ich es schließe.
Mit anderen Worten: Du möchtest dass das Dialogfeld ein Member der Application ist...
Baldur schrieb:
Aber wie sieht es mit den Buttons oder Textfeldern auf dem Dialogfeld aus? Eigentlich möchte ich doch lieber, daß die mit dem Dialogfeld direkt mit gelöscht werden, und mir keine Liste mit Objekten merken, bzw. wann die gelöscht werden dürfen.
Mit anderen Worten: Du möchtest dass die Buttons und Textfelder Member des Dialogfeldes sind...
Baldur schrieb:
Oder ich will einen Scene-Wechsel mit Hilfe einer Transition durchführen, dann möchte ich, daß die Transition mein altes Scene-Objekt löschen kann, sobald die Transition beendet ist.
Lässt sich z.B. über std::unique_ptr machen. Was du da beschreibst, klingt mir eher nach Ownership-Transfer und nicht nach shared Ownership...
Baldur schrieb:
Und natürlich gibt es Fälle wie den obigen, daß z.B. ein TouchHandler ein Objekt behalten kann, was sonst bereits gelöscht wäre.
Wieso genau muss der TouchHandler das so machen?
-
So ganz das Ursprungsthema ist es ja nicht, aber ich will mal versuchen es zu erklären.
Das mit dem Dialogfeld war ein Beispiel. Ein anderes Beispiel wären Resourcen wie Images oder Texturen, die von mehreren Objekten verwendet werden, und gelöscht werden sollen, wenn das letzte Objekt, das es verwendet, stirbt.
Sicher kann ich vieles davon auch mit shared_ptr, unique_ptr, etc umsetzen, allerdings hat es bisher auch recht gut mit dem bisherigen System funktioniert. Ja, es kann passieren, daß ich ein retain oder release vergessen, bisher war das aber tatsächlich nie ein größeres Problem. Vergessene releases ließen sich mit valgrind immer sehr effektiv auffinden.
Aber wie gesagt, ich bin besseren Lösungen gegenüber ja durchaus offen, wenn es sich wirklich als besser erweist. Bisher wurden hier aber nicht viele Argumente genannt, aueßr eben daß es automatisch geht.
Nun, wo sich das Thema schon in eine Diskussion über Smartpointer geändert hat, wäre dann wohl meine Frage, ob mit shared_ptr auch etwas in der Art umsetzbar wäre (wie gesagt, hab mich noch nicht ganz so tief damit auseinander gesetzt, ist jetzt auch eher ein wenig Pseudo-Code, weil ausm Kopf geschrieben)
class Node { void addChild(shared_ptr<Node*> child) } ... shared_ptr<MyMagicButton*> button = new MyMagicButton(); myNode->addChild(button);
-
Ja, das funktioniert - abgesehen davon, dass man dem shared_ptr nicht den Zeigertyp sondern den Typ, auf den gezeigt werden soll übergibt. Und was intrusive Zeiger angeht, kannst du das in einen intrusive_ptr wrappen, dann hast du intrusives Reference Counting und trotzdem automatisch.
-
@314159265358979
Dein hier skizzierter shared_ptr ist intrusive, denn man kann nicht mehr einfach Objekte die man von irgendwoher bekommt reinstecken.@Baldur
Ja, man kann ganz normal () casten und Upcasts sind weiterhin implizit. ( man muss nur boost::xxx_pointer_cast<>() statt xxx_cast<>() verwenden)Was nicht mehr geht (oha, ein Nachteil von shared_ptr der ja angeblich keine Nachteile hat, na sowas), ist ... wenn du nen rohen Zeiger bekommst kannst du keinen shared_ptr mehr draus machen, d.h. du kannst kein "retain" machen.
-
hustbaer schrieb:
Was nicht mehr geht (oha, ein Nachteil von shared_ptr der ja angeblich keine Nachteile hat, na sowas), ist ... wenn du nen rohen Zeiger bekommst kannst du keinen shared_ptr mehr draus machen, d.h. du kannst kein "retain" machen.
_Was genau_ geht hier angeblich mit shared_ptr nicht?
-
314159265358979 schrieb:
hustbaer schrieb:
Was nicht mehr geht (oha, ein Nachteil von shared_ptr der ja angeblich keine Nachteile hat, na sowas), ist ... wenn du nen rohen Zeiger bekommst kannst du keinen shared_ptr mehr draus machen, d.h. du kannst kein "retain" machen.
_Was genau_ geht hier angeblich mit shared_ptr nicht?
Du kannst diese Funktion nicht implementieren:
// p zeigt auf ein Objekt das bereits von einem shared_ptr kontrolliert wird shared_ptr<Thing> GetSharedPtr(Thing* p) { return ...? }
Mit einem klassischen intrusive_ptr ist das überhaupt kein Problem.
-
enable_shared_from_this...
-
hustbaer schrieb:
314159265358979 schrieb:
hustbaer schrieb:
Was nicht mehr geht (oha, ein Nachteil von shared_ptr der ja angeblich keine Nachteile hat, na sowas), ist ... wenn du nen rohen Zeiger bekommst kannst du keinen shared_ptr mehr draus machen, d.h. du kannst kein "retain" machen.
_Was genau_ geht hier angeblich mit shared_ptr nicht?
Du kannst diese Funktion nicht implementieren:
// p zeigt auf ein Objekt das bereits von einem shared_ptr kontrolliert wird shared_ptr<Thing> GetSharedPtr(Thing* p) { return ...? }
Mit einem klassischen intrusive_ptr ist das überhaupt kein Problem.
Wieso sollte man sowas machen? Aber trotzdem:
void null_deleter(void*){} shared_ptr<Thing> GetSharedPtr(Thing* p) { return shared_ptr<Thing>(p, &null_deleter); }
Ist auch nicht schwachsinniger als die Ausgangssituation...
-
Deine Lösung ist übrigens falsch Tachyon, da der Besitz auch wirklich geteilt werden muss. Denn sonst wird das Objekt gelöscht, wenn der letzte shared_ptr gelöscht wird, von dem der Zeiger kommt.
-
Tachyon schrieb:
void null_deleter(void*){} shared_ptr<Thing> GetSharedPtr(Thing* p) { return shared_ptr<Thing>(p, &null_deleter); }
Ich denke, hustbaer ging es darum, dass beim Löschen des ursprünglichen shared_ptr dieser neue shared_ptr das Objekt am Leben halten soll.
-
314159265358979 schrieb:
Deine Lösung ist übrigens falsch Tachyon, da der Besitz auch wirklich geteilt werden muss. Denn sonst wird das Objekt gelöscht, wenn der letzte shared_ptr gelöscht wird, von dem der Zeiger kommt.
Ich habe den Thread gar nicht gelesen, um ehrlich zu sein. Aber das es ohnehin Quark ist, habe ich ja bereits angedeutet.
-
Natürlich, wenn shared_ptr, sollte sich das schon konsequent durch die ganze API ziehen, so daß alle entsprechenden Funktionen nur Objekte in shared_ptr annehmen (gibts eigentlich sinnvolle Möglichkeiten, zu erzwingen, daß ich z.B. von Node oder deren Unterklassen keine Instanzen ohne shared_ptr erstellen kann?)
Wie es aussieht, müsste ich mich dann selbst um eine Smartpointer Implementierung kümmern, da ich im Android NDK keine Boost zur Verfügung habe.
-
Baldur schrieb:
gibts eigentlich sinnvolle Möglichkeiten, zu erzwingen, daß ich z.B. von Node oder deren Unterklassen keine Instanzen ohne shared_ptr erstellen kann?
Vielleicht was mit privaten ctor und ner Factory als friend? Aber ich fänd' es generell erstmal ziemlich gemein, wenn eine Klasse, die ich eigentlich gerne benutzen möchte, mich so sehr bevormunden würde.
Edit: Sorry, Schwachsinnsidee, da es ja auch um Ableitungen geht.
-
314159265358979 schrieb:
enable_shared_from_this...
enable_shared_from_this
ist wieder intrusive (man muss die Pointee-Klasse modifizieren).
Implementiere es ohne "Thing" zu modifizieren.Baldur schrieb:
gibts eigentlich sinnvolle Möglichkeiten, zu erzwingen, daß ich z.B. von Node oder deren Unterklassen keine Instanzen ohne shared_ptr erstellen kann?
Jain.
Wenn die abgeleiteten Klassen "mittun", dann geht das sehr einfach: verpass der Node-Klasse eine Factory-Funktion (idealerweise gleich ein Template), und mach den Ctor von Node und allen abgeleiteten Klassen protected bzw. private.Wenn die abgeleiteten Klassen nicht "mittun" hast du denke ich keine Chance.
Folgendes findet sich z.B. in einem meiner Projekte
template <class T> inline shared_ptr<T> Control::CreateControl(shared_ptr<GuiSystem> const& guiSystem) { shared_ptr<T> control(new T(guiSystem), Deleter()); control->Control::PrivateDoPostConstruction(); return control; } template <class T, class A1> inline shared_ptr<T> Control::CreateControl(shared_ptr<GuiSystem> const& guiSystem, A1 const& a1) { shared_ptr<T> control(new T(guiSystem, a1), Deleter()); control->Control::PrivateDoPostConstruction(); return control; } template <class T, class A1, class A2> inline shared_ptr<T> Control::CreateControl(shared_ptr<GuiSystem> const& guiSystem, A1 const& a1, A2 const& a2) { shared_ptr<T> control(new T(guiSystem, a1, a2), Deleter()); control->Control::PrivateDoPostConstruction(); return control; } // ... versionen mit 3, 4, 5 parametern inline void Control::PrivateDoPostConstruction() { m_lifecyclePhase = LifecyclePhase_PostConstruction; PostConstructor(); m_lifecyclePhase = LifecyclePhase_FullyInitialized; AssertPostConstructorCalled(); }
(Ist für VS 2005, daher kein perfect forwarding, variadic template etc.)
Abgeleitete (GUI-)Controls die nicht als Basisklasse taugen machen ihren Ctor private und erklären
Control
zum Freund. Und abgeleitete Controls die als Basisklasse taugen machen den Ctor protected.ps @all
Wenn das so schlimmer Quark wäre gäbe es vermutlichenable_shared_from_this
nicht. Das Argument dass man sowas nicht braucht wenn man ordentlich arbeitet lasse ich also in diesem Fall nicht gelten.
-
hustbaer schrieb:
314159265358979 schrieb:
enable_shared_from_this...
enable_shared_from_this
ist wieder intrusive (man muss die Pointee-Klasse modifizieren).
Implementiere es ohne "Thing" zu modifizieren.Aber nur, wenn man diese Funktionalität braucht. Bei intrusivem Referece Counting muss man das mit jeder Klasse machen.
Ansonsten kann man natürlich auch noch eine globale Liste an weak_ptr führen. Der Sinn davon sei mal dahingestellt.
-
314159265358979 schrieb:
Aber nur, wenn man diese Funktionalität braucht. Bei intrusivem Referece Counting muss man das mit jeder Klasse machen.
Richtig.
Ansonsten kann man natürlich auch noch eine globale Liste an weak_ptr führen. Der Sinn davon sei mal dahingestellt.
Ja, irgendwie kann man das schon reinprügeln. Hätte aber mehr Nachteile als Vorteile.
Weitere Vorteile von Intrusive Ref Counting:
Man kann es unglaublich einfach implementieren (-> KISS).
Man kann es (u.a. dadurch) schön in DLL Schnittstellen verwenden, in denen man (aus welchem Grund auch immer) keine Dependency auf diverse Libs wie Boost, CRT usw. haben darf.
Man kann es dadurch auch schön Sprachübergreifend verwenden.Die einzige Möglichkeit die ich kenne, wie man sowas mit einer nicht-intrusive Lösung hinbekommt, ist globale Tables zu verwenden. Was viele viele Nachteile mitbringt, weswegen ich das für die meisten realen Projekte als "no go" klassifizieren würde.
Viel davon geht auch mit
shared_ptr
undenable_shared_from_this
, aber nur mit mMn. schwer zu vertretendem Aufwand. Vor allem wenn man keinen echten Vorteil davon hat. Und den hat man in vielen Projekten nicht.Wenn man Features wie
weak_ptr
braucht, dieshared_ptr
bereits mitbringt, man sich mit einer (selbstgebackenen) intrusive Lösung aber umständlich dazustricken müsste, sieht die Sache wieder anders aus.Jetzt Frage: bist du immer noch der Meinung dass Intrusive Ref Counting keinerlei Vorteile hat?
Das war nämlich deine Behauptung die ich kritisiert habe.Etwas diplomatischer formuliert wie z.B. "Intrusive Ref Counting hat wenig relevante Vorteile, und i.A. fährt man mit shared_ptr besser" wäre ja durchaus OK, da hätte ich sicher nix dagegen gesagt.
Weil du Pi bist musst du aber immer mit absoluten Aussagen und Superlativen um dich werfen. Und dann passiert das, was hier wiedermal passiert ist. Weil ich das dann so nicht stehen lassen kann.
-
Wo das Thema ja hier sowieso schon etwas abgedriftet ist, aber die Sache mit Ownership angesprochen wurde, wollte ich mal fragen, wie ihr das so handhabt. Es wurde ja in den Raum gestellt, dass ein Objekt seine eigene Lebenszeit niemals verwaltet. Im Prinzip würde ich dem ja auch direkt zustimmen, weil es anderenfalls zu Fehlern kommt, wenn man es "falsch" verwendet.
Sagen wir mal, wir haben so ein UI-Programm und auf einen Mausclick wird ein Widget erzeugt, das die Maus captured und sich mit dem nächsten Click und einer änderung am Datenmodell dahinter wieder verabschieden soll. Der erste Click geht ja zweifellos an ein anderes Ziel als dieses Widget, dieses erzeugt dann das Widget (bitte keine 2-Phasen-Erzeugung). Nun soll aber das erzeugte Widget die Abhandlung der nächsten Benutzereingaben durchführen. Da kommt man doch zu dem Problem, dass das Widget auf eine Benutzereingabe-Nachricht entscheiden soll, dass es fertig ist. Wer ist nun dafür zuständig, es zu entfernen? Soll es sich beim Erzeuger melden, dass es fertig ist? (Creator()->DeleteMe())? Das entspricht ja nun auch nur einem verkappten delete this.
Gleiches Beispiel hätte man ja nun auch bei jedem beliebigen Dialog, der einen Schließen-Button aufweist.Wäre da mal an der sauberen (tm) Lösung interessiert!
Viele Grüße,
Michael