SmartPointer Design Frage



  • [quote="knivil"]
    Du hast mir dem Vergleich COM angefangen, nicht ich./quote]
    lol?? Was isn das für eine "Logik"? Du kamst damit an, dass die Factory ein destroy() anbieten soll. Dann habe ich gesagt das ich das nicht so machen will und COM als Beispiel genannt, dass das so macht wie ich auch. Und jetzt zitierst du willkürlich ein COM Code Schnipsel, das nur das zeigt was ich bereits gesagt habe. Omg...

    Mir ging es aber auch nicht um den SmartPointer, der kann ruhig das Objekt vernichten, wenn der Zaehler auf null faellt, DecRef() aber nicht.

    Du hast offenbar noch immer nicht verstanden, dass Reference Counting auch ohne Smart Pointer funktioniert. Siehe COM. Du solltest dir wirklich mal boost::intrusive_ptr anschauen, bevor du hier weiter nervst.

    Wie gesagt, über Meinungen ANDERER User freu ich mich dennoch;)



  • marld schrieb:

    Ich tendiere mittlerweile auch dazu einen SmartPtr zurückzugeben. Der einzige Nachteil wäre eben, dass der Benutzer nicht mehr ganz so elegant auch ohne SmartPtr arbeiten könnte.

    Er kann ja so oder so nicht ohne SmartPtr arbeiten, da Reference-Counting und Creation/Deletion ja verschmolzen sind (das ist eigentlich nicht sonderlich schön, da muss ich knivil absolut zustimmen).
    Ich würde jedenfalls entweder voll auf SmartPtr setzen oder die beiden Konzepte entkoppeln. Ein Zwischending funktioniert imho nicht gut.



  • Wo ist das denn bitte verschmolzen? Der Benutzer kann vollständig ohne SmartPtr arbeiten.



  • 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 => delete
    

    Das 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 das

    Packet* 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 => delete
    

    Dafü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 => delete
    

    Wie wird denn das Objekt vernichtet? Vielleicht so:

    delete this
    

    Du bist verdammt! 🙂



  • knivil schrieb:

    f->DecRef();    // Foo ref count == 0 => delete
    

    Wie wird denn das Objekt vernichtet? Vielleicht so:

    delete this
    

    Du 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.


Anmelden zum Antworten