Designfrage: Mehrfachvererbung / Interfaces
-
314159265358979 schrieb:
Bei shared_ptr lässt er sich immer vermeiden, vorausgesetzt, man hat eine ordentliche Implementierung. Die std-Implementierung gibt da zu wenige Garantien.
Dein shared_ptr ist aber rein prinzipiell immer doppelt so groß wie ein vergleichbarer Smartpointer für intrusives Ref Counting, weil er zwei Pointer halten muss. Mit make_shared kannst du nur den Overhead eines zusätzlichen new sparen...aber wenn du meinst...
-
314159265358979 schrieb:
Bei shared_ptr lässt er sich immer vermeiden, vorausgesetzt, man hat eine ordentliche Implementierung. Die std-Implementierung gibt da zu wenige Garantien.
Ach was, der SharedPointer owned den IntrusivePointer per Default!!! Alta, was kommste da mit deinem Ausreden.
-
Wenn man nur die shared_ptr Funktionalität ohne weak_ptr und Deleter braucht, könnte man sich sowas basteln (nur angedeutet, unvollständig):
#include <iostream> #include <utility> #include <cstddef> template <typename T> struct my_shared_ptr { my_shared_ptr() : rc(0) {} template <typename... Args> my_shared_ptr(Args&&... args) : rc(new refcount_block(std::forward<Args>(args)...)) {} T& operator * () { return *rc->ptr(); } T* operator -> () { return rc->ptr(); } ~my_shared_ptr() { if(!--rc->refs) delete rc; } private: struct refcount_block { template <typename... Args> refcount_block(Args&&... args) : refs(1) { new (object) T(std::forward<Args>(args)...); } ~refcount_block() { ptr()->~T(); } T* ptr() { return reinterpret_cast<T*>(object); } std::size_t refs; char object[sizeof(T)]; // alignas(T) }; refcount_block* rc; }; int main() { std::cout << sizeof(int*) << '\n' << sizeof(my_shared_ptr<int>) << '\n'; }
http://ideone.com/aIlrW
Kann genau das, was man mit intrusivem Referenzzählen auch machen kann...Edit: Das mit dem rohen char-Array ist natürlich Blödsinn, ein normaler T geht auch. Aber ich bin jetzt zu faul, das noch zu ändern. :p
-
Damit hast du nun einfach das make_shared fix in den shared_ptr gebaut, was semantisch etwas anderes ist. Dein Pointer kümmert sich nun nicht nur um das Verwalten, sondern auch um das Erzeugen der Objekte...
-
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.