Smart Pointer
-
TNA schrieb:
Vielleicht weil er std::weak_ptr benötigt und die nicht in Kombination mit std::unique_ptr funktionieren?
weak_ptrbenötigt man nie einfach so, sondern immer nur in Verbindung mitshared_ptr.Aber dann frag ich eben anders: Warum reicht
unique_ptrhier nicht aus?
-
Hmm, das stimmt natürlich. Wenn sichergestellt ist, das ein Child den Parent nie überlebt und das so gewünscht ist, sollte das passen. Wenn das multithreaded ist dürfte das allerdings eventuell sehr kritischen, schwierig zu behebenden Race Conditions führen.
-
Enumerator schrieb:
jeder Parent-Node auf seinen Child-Node verweist und umgekehrt
Hab ich jetzt was nicht ganz verstanden oder wie soll man das mit einem unique_ptr hinbekommen?
-
Ein normaler Zeiger als Child->Parent.
-
anti-freak schrieb:
Enumerator schrieb:
jeder Parent-Node auf seinen Child-Node verweist und umgekehrt
Hab ich jetzt was nicht ganz verstanden oder wie soll man das mit einem unique_ptr hinbekommen?
"Umgekehrt" ist weiterhin ein normaler Pointer. Warum sollte es auch ein smart pointer sein?
-
Du solltest dir klar sein, was die pointer bedeuten.
unique_ptr bedeutet, das Objekt hat einen klaren Besitzer
shared_ptr bedeutet, das Objekt hat mehrere Besitzer
weak_ptr kann auf ein geteiltes Objekt zugreifen, aber besitzt es nicht.
Ein normaler pointer zeigt nur und hat keine weiteren BedingungenWie Kellerautomat und manni64 geschrieben haben, wenn du davon ausgehst, dass ein Child NUR EINEN Parent haben kann und bei dessen Löschen auch gelöscht wird, dann besitzt der Parent das Child, also unique_ptr.
Das Child greift nur auf sein Parent zu, aber weder löscht, noch räumt es seinen Parent auf (wie auch, schließlich sollte es andersherum sein), dann gehört hier ein normaler pointer hin.Würde man für beides shared_ptr verwenden, hätte man hier circular references, wodurch die beide Objekte ewig bestehen bleiben würden: memory leaks oder andere blöde Nebeneffekte.
-
TNA schrieb:
Wenn sichergestellt ist, das ein Child den Parent nie überlebt und das so gewünscht ist, sollte das passen. Wenn das multithreaded ist dürfte das allerdings eventuell sehr kritischen, schwierig zu behebenden Race Conditions führen.
Die Aussage ist so für sich genommen mMn. Quatsch.
Wenn du anderer Meinung bist, dann beschreibe bitte wenigstens einen Fall der hier Probleme verursachen könnte. (Inklusive Begründung warum der selbe Fall ohne Multithreading zu keinem Problem führt, sofern dies nicht offensichtlich sein sollte.)
-
Wenn ein Thread gerade eine Operation auf einer Node ausführt bei der der Parent Zeiger involviert ist während ein anderer Thread gerade den Parent entfernt.
-
TNA schrieb:
Wenn ein Thread gerade eine Operation auf einer Node ausführt bei der der Parent Zeiger involviert ist während ein anderer Thread gerade den Parent entfernt.
Da werden dir smart pointer als Zeiger auf den Parent nicht weiterhelfen, denn den musst du ja auch irgendwann umsetzen bzw. löschen. Du wirst nicht drumrum kommen die Zugriffe zu synchronisieren.
-
manni66 schrieb:
TNA schrieb:
Wenn ein Thread gerade eine Operation auf einer Node ausführt bei der der Parent Zeiger involviert ist während ein anderer Thread gerade den Parent entfernt.
Da werden dir smart pointer als Zeiger auf den Parent nicht weiterhelfen, denn den musst du ja auch irgendwann umsetzen bzw. löschen. Du wirst nicht drumrum kommen die Zugriffe zu synchronisieren.
Warum? Wenn ich einen Weak Pointer auf den Parent habe hole ich mir daraus einen shared Pointer. Entweder das schlägt fehl, dann weiß ich, das der Parent weg ist oder eben nicht, dann kann ich sicher sein, dass der Parent noch mindestens so lange lebt wie ich den Ponter in der Hand habe. Für das Problem "Mir zieht jemand die Node unterm Hintern weg" braucht man dann keine Synchronisierung. Synchronisierung braucht man zwar eventuell an anderer Stelle trotzdem, kann dann aber sehr viel fein granularer Synchronisieren.
Hab gerade deinen Betrag noch mal genau gelesen. Wenn man bestehende Nodes umsetzen will und nicht nur löschen/einfügen geht das wohl tatsächlich nicht mehr ohne explizites synchronisieren.
-
Die Anwendung ist noch single threaded wird aber später multithreaded laufen. Es kommt noch schlimmer, es ist ein GUI Framework mit Styles die wiederum auf andere Resourcen verlinken können. Resourcen aber auch wieder auf andere Resourcen. Auch die Controls sind natürlich miteinander verlinkt. D.h. in der Regel ist der Owner von z.B. einer Resource nicht eindeutig und Unique-Pointer kommen schonmal gar nicht in Frage. Also auf gut deutsch diese ganzen SmartPointer sind in Wirklichkeit ziemlich dumm und lesen sich nur auf dem Papier gut. In der Praxis sind sie eine jämmerliche Krücke um die ich wild herumfrickeln müsste. Einheitlich kann ich das ganze auch nicht lösen. Damit sind "SmartPointer" vom Tisch. Dazu kommt der wahrscheinlich hohe Performanceverlust. Bleibt also nur wie in dem anderen Thread von mir genannte Garbage Collection. Was vielleicht noch ginge: Alle Klassen sind von einer gemeinsamen Basis-Klasse abgeleitet. Kann ich nicht in dieser Basis-Klasse einen Mechanismus implementieren der das löschen regelt?
-
Enumerator schrieb:
Einheitlich kann ich das ganze auch nicht lösen. Damit sind "SmartPointer" vom Tisch. Dazu kommt der wahrscheinlich hohe Performanceverlust.
SmartPointer sind in dem was sie tun extrem performant. Glaubst du wirklich, du bekommst diese Funktionalität, wenn du sie wirklich brauchst, irgendwie billiger, zumal du offenbar noch viel komplexere Sachen machen möchtest als mit den SmartPointern möglich? Mit einem GC sicherlich nicht.
-
Aus einem WeakPtr zunächst einen SharedPtr zu erzeugen um festzustellen um ein Objekt existiert ist in meinen Augen nicht gerade performant. Das würde mir den Renderingprocess vermutlich extrem ausbremsen. Der GC könnte dagegen wenn das Programm gerade Idle ist was bei einer GUI oft der Fall ist aufräumen.
-
Enumerator schrieb:
Es kommt noch schlimmer, es ist ein GUI Framework mit Styles die wiederum auf andere Resourcen verlinken können. Resourcen aber auch wieder auf andere Resourcen. Auch die Controls sind natürlich miteinander verlinkt.
Ist das Framework für C++ oder für eine andere Sprache?
Bei Controls sehe ich nicht ein, wieso der Parent nicht das Child besitzen kann. Qt macht das zum Beispiel so. unique_ptr heisst ja nicht, dass der Besitz nicht wechseln kann, Besitz ist nur ein temporäres Verhältnis.
Styles die auf Resourcen verlinken sind auch in Ordnung und Resourcen, die untereinander verlinkt auch, aber nur, wenn keine zirkuläre Verlinkung da ist. Was soll ich mir unter zwei Ressourcen, die sich gegenseitig verlinken vorstellen? Da reichen shared_ptrs und Performance ist auch nicht so wichtig, da das eher statisch sein sollte. Ansonsten machst du dir halt einen eigenen Memory-Pool, der refcounted ist.
-
Enumerator schrieb:
Aus einem WeakPtr zunächst einen SharedPtr zu erzeugen um festzustellen um ein Objekt existiert ist in meinen Augen nicht gerade performant. Das würde mir den Renderingprocess vermutlich extrem ausbremsen. Der GC könnte dagegen wenn das Programm gerade Idle ist was bei einer GUI oft der Fall ist aufräumen.
Ist dieser Baum überhaupt der Flaschenhals bzw. ist das ganze so performancekritisch? Braucht man überhaupt ständig den Parent Zeiger? Darüber würde ich mir erst mal im klaren sein wollen, bevor ich viel Zeit in die Lösung von Problemen stecke die vielleicht gar nicht existieren. Selbst wenn die Antwort Ja sein sollte, hilft hier auch nur messen.
-
Was macht weak_ptr::lock denn genau?
Der guckt doch nur ob sein zugehöriger elterlicher shared_ptr (noch) da ist, kopiert diesen dann und gibt die Kopie an den Aufrufer zurück.Wenn das wirklich der Part sein sollte, der deiner Performance das Genick brechen sollte, dann hast du an anderen Stellen eher geschlampt.
-
Skym0sh0 schrieb:
Was macht weak_ptr::lock denn genau?
Der guckt doch nur ob sein zugehöriger elterlicher shared_ptr (noch) da ist, kopiert diesen dann und gibt die Kopie an den Aufrufer zurück.Exakt, das dürfte optimalerweise gerade eine atomic-Operation auf den Refcounter sein (Testen+Hochzählen). dazu noch einmal Pointer kopieren. Aber hier gilt wie immer: Im Zweifelsfall selber messen, alles andere ist Spekulation.
-
D.h. anstatt wie in meiner bisherigen Denkweise zu sagen, dass ein mehrfach verlinktes Objekt keinen eindeutigen Owner hat, sage ich nun, dass z.B. ein Control immer zu seinem Parent-Control gehört. Genauso gehört eine Resource dem ResourceDictionary usw. Alle anderen Verlinkungen sind also nur "weak".
Das habe ich bislang auch so praktiziert und das funktioniert auch soweit. Sogar mit rohen Zeigern die ich im Destruktor deleten lasse.
Jetzt kommt aber das Property-System ins Spiel. Ein Control hat ein Style-Property. Mal angenommen der User möchte also, dass sein Control anders ausschaut und weist ihm einen neuen Style zu. Dieser wird nun dem Property-System übergeben. Blöderweise weiß dieses jetzt aber nicht, ob dieser bzw. der davor zugewiesene Style ein Style ist, der womöglich zu einem ResourceDictionary gehört oder nicht. Würde jetzt der vorherige Style der zu einem ResourceDictionary gehört zerstört hätte ich ein Problem. Genauso hätte ich ein Problem wenn der vorherige Style nicht zerstört wird, aber nur zu diesem Control gehörte. Ein Style kann also zu einem ResourceDictionary gehören, kann aber auch nur einem Control gehören. Ein UniquePtr wäre hier also schonmal fehl am Platz? Wenn dann muss ich dort einen SharedPtr verwenden? Aber das wäre dann ja auch kein Problem da es keine zirkulare Verlinkung ist.
Kurz und knapp gesagt muss ich also alle starken Verbindungen sowie alle Properties als SharedPtr implementieren, die schwachen als WeakPtr oder gleich als rohen Zeiger belassen. Ist das so richtig oder mache ich da einen Gedankenfehler?
Denn im Prinzip gefällt mir an den SmartPointern, dass ein Objekt gleich zerstört wird, wenn es nicht mehr verwendet wird anstatt es wie bei einem GC erstmal im Speicher zu horten.
-
Doch nochmal eine Frage. Ein Control kann theoretisch auch ein ResourceDictionary besitzen in dem sich wiederum ein Style befinden kann der dann dem Control zugeordnet wird. Ist das nun eine zirkulare Verlinkung?
-
Auf deinen "großen" post hin, ja. Alle starken referenzierungen nutzen shared_ptr und alle schwachen nutzen weak_ptr. Im Prinzip kannst du erstmal überall shared_ptr nutzen, nur wenn du merkst, dass es einen Zyklus gibt, ersetzt du einen der beiden durch einen weak_ptr.
Zu deiner zweiten Frage:
Nein, was du da beschreibst klingt mehr nach zyklischer Abhängigkeit zweier Strukturen, was erstmal kein Problem ist (bestimmt kommt gleich einer und sagt, dass es schlechtes Design ist).Auch für shared_ptr ist es kein Problem, wenn es zyklische Abhängigkeiten gibt.
Stell dir mal den Fall vor:
#include <iostream> #include <memory> using namespace std; struct foo { int i; shared_ptr<foo> myf; foo(int i) : i(i) { cout << "Objekt: " << i << endl; } ~foo() { cout << "Kaputt: " << i << endl; } }; int main() { shared_ptr<foo> f(new foo(0)); { shared_ptr<foo> pf1(new foo(1)); shared_ptr<foo> pf2(new foo(2)); pf1->myf = pf2; pf2->myf = pf1; } return 0x0; }Das ist eine zyklische Referenzierung. Kannst du mir sagen, was da ausgegeben wird?