SmartPointer Design Frage
-
marld schrieb:
Wo ist das denn bitte verschmolzen? Der Benutzer kann vollständig ohne SmartPtr arbeiten.
Foo* f = createFoo(2); // Foo hat ref count == 1 // Benutze foo f->DecRef(); // Foo ref count == 0 => deleteDas DecRef ist doch ein SmartPtr-Ding. Ohne SmartPtr sähe es so aus:
Foo* f = createFoo(2); // Foo hat ref count von "undefiniert" // Benutze foo destroyFoo( f );Und konsequent mit SmartPtr so:
SmartPtr<Foo> f = createFoo(); // Benutze foo // Hier wird automatisch abgeräumt
-
marld schrieb:
Wo ist das denn bitte verschmolzen? Der Benutzer kann vollständig ohne SmartPtr arbeiten.
dann halt intrisive. wenn die klasse die intrusion über sich ergehen ließ, dann soll die factory auch einen smart pointer zurückgeben und nichts anderes. der empfänger des smart pointers und besitzer des objekts kann dann immernoch entscheiden, daß er an seine diener rohe zeiger verschickt, weil er eh der besitzer bleiben will.
normalerweise werden eh nur klassen smartpointerbefähigt, die vermutlich herumgeschubst werden, wie netzwerkpakete oder tokens oder jobs.
außerdem ist ja noch dasPacket* packet=factory.create();//kein smart pointer, ich bin ein berliner tor foo(); //dadrin passiert ein throw //speicherloch
-
Badestrand schrieb:
Das DecRef ist doch ein SmartPtr-Ding.
Nein, eben nicht! DecRef() und IncRef() sind lediglich 2 Methoden, die eine Oberklasse Object anbietet, von der alle Klassen ableiten. Das hat NICHTS mit einem Smart Pointer zu tun!
volkard schrieb:
dann halt intrisive. wenn die klasse die intrusion über sich ergehen ließ, dann soll die factory auch einen smart pointer zurückgeben und nichts anderes
Japp, so werd ichs jetzt auch machen.
-
Foo* createFoo(int i) { Foo* p = new Foo(i); p->IncRef(); // Ref count == 1 return p; } // Anwendung: Foo* f = createFoo(2); // Foo hat ref count == 1 // Benutze foo f->DecRef(); // Foo ref count == 0 => deleteDafür refCounting ist doch echt nicht nötig...
Foo produziert immer neue Objekte, also eine nicht denkende, blöde factory.
Diese werden dann benutzt. Werden die auch irgendwie weitergegeben? Also im Kontext einer Klasse per getter angeboten? Dann macht ReferenceCounting schon Sinn. Ansonsten ist es nur unnötiger Ballast, und hat mMn nix in der Klasse selber zu suchen.
Außerdem, wenn RefCounting, dann automatisiert. Wenn ein Benutzer deiner Klasse(n) vergisst, die per createFoo erzeugten Objekte per DecRef() freizugeben, oder am Ende meint "Ich hab Pointer, weg damit -> delete foo;", hast du ziemlichen Stress. Memleaks oder SegFault.Darum würde ich den RefCounter aus dem Objekt rausnehmen und entweder nur Pointer produzieren, und dem User das freigeben überlassen, oder eben gleich nen SmartPointer zurückgeben.
Ich tendiere eher zu normalen Pointern.
-
f->DecRef(); // Foo ref count == 0 => deleteWie wird denn das Objekt vernichtet? Vielleicht so:
delete thisDu bist verdammt!

-
knivil schrieb:
f->DecRef(); // Foo ref count == 0 => deleteWie wird denn das Objekt vernichtet? Vielleicht so:
delete thisDu bist verdammt!

Ja klar so. Wo soll das Problem sein?
-
Ich finde es ehrlich gesagt auch unschön, über
Foo* f = createFoo(2);eine Instanz zu erstellen, die man mit
f->DecRef();wieder löscht. Was spricht denn gegen eine einheitliche Schnittstelle zur Erzeugung und Zerstörung von Instanzen, also etwas wie
destroyFoo(f);? (Das ist einfach meine Ansicht, ich würde das nie so machen...)
-
einheitliche Schnittstelle zur Erzeugung und Zerstörung von Instanzen
Das ist das Symmetrieprinzip, von dem ich rede.
Ja klar so. Wo soll das Problem sein?
delete this halte ich fuer grundsaetzlich boese, ganz einfach weil wieder das Symmetrieprinzip verletzt ist. Ein Objekt kann sich nicht selber erzeugen, also sollte es sich auch nicht selbst zerstoeren. Sauber programmiert man anders. Auch kann das Objekt auf dem Stack liegen, was vielelicht hier nicht der Fall sein wird/kann. Auch weiss ich nicht, wie ob Code nach einem delete this (z.B. in Form von einem return) undefiniertes Verhalten erzeugt. Normalerweise ist dieses Konstrukt ueberfluessig und kann durch besseres Programmdesign entfernt werden.
-
knivil schrieb:
delete this halte ich fuer grundsaetzlich boese, ganz einfach weil wieder das Symmetrieprinzip verletzt ist. Ein Objekt kann sich nicht selber erzeugen, also sollte es sich auch nicht selbst zerstoeren. Sauber programmiert man anders.
Mag sein, aber ich hab mal gelesen, dass wenigstens unter win dies die einzige Möglichkeit ist, ein via LoadLibrary dynamisch geladenes Objekt (Plugin) ordentlich zu löschen, das muss einfach aus dem Speicherbereich der dll heraus geschehen. Weshalb ich mittlerweile im Plugin-Interface den Destruktor + destroy()-Methode private mache und den PluginManager als friend deklariere, welcher seinerseits eine Methode zum löschen anbietet... Damit garantiert nix falsch geht.
-
Ich kann dir nicht folgen: Die DLL/Plugin (fuer was eigentlich) or whatever kann fuer Objekte einfach create und destroy anbieten. Ich verwende also fuer Objekte der DLL die Funktionen der DLL zum Erzeugen und Vernichten. Zumindestens implementiere ich es so bei .so-Bibliotheken unter Linux.
hab mal gelesen
Eine etwas schwache Argumentation. Vielelicht war es einfach schlechtes Plugindesign. Und dein Beispiel ist eher speziell. Im Allgemeinen wuerde ich immer nach besseren Moeglichkeiten als delete this suchen. Hier gibt es die bestimmt.
LoadLibrary dynamisch geladenes Objekt
Keine Ahnung wie Windowsprogrammierung so aussieht, aber ladet man nicht Bibliotheken anstatt Objekte?
-
knivil schrieb:
delete this halte ich fuer grundsaetzlich boese, ganz einfach weil wieder das Symmetrieprinzip verletzt ist. Ein Objekt kann sich nicht selber erzeugen, also sollte es sich auch nicht selbst zerstoeren. Sauber programmiert man anders. (...) Normalerweise ist dieses Konstrukt ueberfluessig und kann durch besseres Programmdesign entfernt werden.
Intrusive Reference Counting ist an sich, von einem bestimmten Standpunkt aus betrachtet, unsauber. Intrusive Reference Counting vermischt Dinge die eigentlich nicht zusammengehören, nämlich die "lifetime control" und das eigentliche Objekt. Es macht die Sache aber nicht besser, die AddRef und Release Funktionen als nicht-Member zu implementieren. IMO macht das die Sache sogar noch schlimmer, denn der Zähler ist Teil des Objekts, also sollte er auch über Member-Funktionen angesprochen werden.
Intrusive Reference Counting ist eine Optimierung, und wie die meisten Optimierungen, streng genommen nicht ganz "sauber". Es ist aber Common Practice und leicht zu verstehen. Daher verstehe ich deine "Bedenken" nicht ganz.
Wenn du es "sauber" willst, dann verzichte ganz auf "Intrusive Reference Counting" ("counted body"), und verwende "counted handle" stattdessen.
Auch weiss ich nicht, wie ob Code nach einem delete this (z.B. in Form von einem return) undefiniertes Verhalten erzeugt.
Es erzeugt kein UB, solange man this danach nichtmehr dereferenziert, oder sonstwie auf Member des Objekts zugreift. Das typische "delete this; return;" ist völlig problemlos.
p.S.: genau das, nämlich dass sich viele Leute nicht sicher sind, ob "delete this" denn "OK" ist, ist der Grund, warum viele Leute es als "böse" verdonnern. Überleg dir mal bessere Argumente, oder hör auf etwas zu verteufeln, was du nicht verstehst.
-
einem bestimmten Standpunkt aus betrachtet, unsauber.
Ich habe verschiedene Standpunkte probiert, leider ist es bei mir von allen Standpunkten fuer dieses Problem unsauber. Auch moechte ich mich nicht vom Speziellen hier dargestelltem Design zum Allgemeinen wegbewegen.
Intrusive Reference Counting vermischt Dinge die eigentlich nicht zusammengehören
Deswegen ist es schlechtes Design.
AddRef und Release Funktionen als nicht-Member zu implementieren
Darum geht es mir nicht.
Es ist aber Common Practice
Ich verweise da gern auf ein Essay von Paul Graham. Hier ein kleiner Auszug:
Within large organizations, the phrase used to describe this approach is "industry best practice." Its purpose is to shield the pointy-haired boss from responsibility: if he chooses something that is "industry best practice," and the company loses, he can't be blamed. He didn't choose, the industry did.
Wenn du es "sauber" willst, dann verzichte ganz auf "Intrusive Reference Counting" ("counted body"), und verwende "counted handle" stattdessen.
Hier gehts wohl weniger um mich.
p.S.: genau das, nämlich dass sich viele Leute nicht sicher sind, ob "delete this" denn "OK" ist, ist der Grund, warum viele Leute es als "böse" verdonnern. Überleg dir mal bessere Argumente, oder hör auf etwas zu verteufeln, was du nicht verstehst.
Bullshit. Meine anderen Argumente hast du ignoriert. Oder welche sind stark genug? Muss erst der Mond auf die Erde stuerzen ... Die Optimierung halte ich hier fuer fragwuerdig, Common Practice siehe oben auch. Und nur weil etwas kein undefiniertes Verhalten erzeugt, heisst es noch lange nicht, das z.B. delete this "OK" ist. Es ist fast nie okay. Wenn man so argumentiert, dann sollte man auch angehende C++ User nicht darauf hinweisen, dass sie Elemente der Standardbibliothek benutzen sollen, wenn sie im C-Stil programmieren.
-
@knivil:
Selber bullshit.Aber glaub was du willst, ich hab grad echt keinen Geist mich mit dir rumzustreiten.
Blub.
-
hier mal meine meinung dazu:
es muss nicht immer symetrisch sein! warum eine extra destroy-factory, wenn
eine Release-funktion das selbe kann? Release ist meist bequemer.(ich gehe jetzt mal von dem DLL / plugin system aus)
ein objekt braucht irgenteine höhere instanz von der sie erzeugt wird. für
ihre vernichtung braucht sie die jedoch nicht. das design funktioniert
auch in der realität, zum aus-dem-fenster-springen brauchst du keine eltern
back to topic. gehen wir mal davon aus, wir haben eine Destroy methode in
einer höheren instanz. diese wird das objekt höchstwahrscheinlich
löschen, also delete obj ausführen. bei einer factory methode ist das normal.warum also kein Release ? praktisch gesehen macht das keinen unterschied wer
das teil jetzt löscht. beides würde über DLL-grenzen hinweg funktionieren.ich selber programmiere unter windows und mit DirectX. mag sein das ich mir
das Release() angewöhnt hab, aber so verkehrt ist das nicht.ich will schließlich nicht immer zwei pointer mit mir rumschleppen.
das müsste ich aber, wenn ich auf die symetrie her achte.zu dem smart_pointer:
meine smartpointer haben ein eigenes Refcounting. wenn sie ref == 0 gehen,
führen sie eine vordefinierte methode aus. im falle von pseudo-COM objekten
halt ein Release. diese methode setzt dann den interen refcounter auf 0,
was dann das löschen zur folge hat.da ich AddRef aber zu fast 0% verwende, kann man sich den zweiten, objektinteren
refcounter sparen. kommt halt drauf an wie deine Release methode aussieht.außerdem kann man ohne probleme besetehenden (DirectX) code verwenden der auf
Release beruht, in dem man das system fortsetzt.auch sollte man sich fragen, wen das ableben eines objektes etwas angeht.
das objekt doch wohl mehr als die factory. das objekt kann intern die factory
ja benachrichtigen, aber es ihr überlassen...das es ein designproblem darstellt, streite ich nicht ab, ist wohl auch so.
allerdings wird sich jeder irgentwann mal eine freistehende Release-funktion
bauen aka:void Release(Factory *fac, Object *obj) { if (fac && obj) fac->DestroyObject(obj); }von da ist es nicht mehr weit, und auch nur logisch, eine Release-memberfunktion
zu bauen.votes["Release"]++;
-
auch sollte man sich fragen, wen das ableben eines objektes etwas angeht.
das objekt doch wohl mehr als die factory. das objekt kann intern die factory
ja benachrichtigen, aber es ihr überlassen...Die Entscheidung, wann ein Objekt zerstört werden sollte, sollte niemals vom Objekt selbst ausgehen. Richtig ist, dass die Factory sich um das aufräumen kümmern muss, weil sie es ist, die den Speicher anfordert. Würde sich das Objekt drum kümmern, und einfach "delete this;" aufrufen, dann kommst du in Teufels Küche, wenn du einmal placement new im Spiel hast(Small Object Allocation als Beispiel genannt). Auch hat das Anrufen der Factory den Nachteil, dass dein Objekt fest an die Factory gebunden ist und ans Reference Counting sowieso. Auch hier: schlechtes Design. in 99,9% der Fälle ist dies nicht nötig, gewollt oder sinnvoll.
meine smartpointer haben ein eigenes Refcounting. wenn sie ref == 0 gehen,
führen sie eine vordefinierte methode aus. im falle von pseudo-COM objekten
halt ein Release. diese methode setzt dann den interen refcounter auf 0,
was dann das löschen zur folge hat.Das ist auch das richtige Design in dem Fall. Damit hast du die Referenzzählung und das Memorymanagement aus dem Objekt verbannt. Das Objekt wird somit vom Smart Pointer zerstört, wenn es niemand mehr braucht. Der SmartPointer wiederum ist der verlängerte Arm der Factory und bekommt von ihr alle Informationen die er braucht, um das Objekt ordnungsgemäß zu zerstören. Dadurch liegen Speicherverwaltung und Lebenszyklus wieder dort wo sie sein sollten: bei der Factoryklasse.

Zu deinem DirectX Beispiel: Teufelsküche^2. Refcounting ist dort ziemlich kompliziert, weil der Refcount nicht nur vom Objekt selber, sondern auch von den vom Objekt erzeugten Objekten abhängt. Eigentlich ist DirectX DAS Beispiel gegen Refcouting.