Pointer-to-impl und smart ptr



  • @camper

    Ich verstehs immer noch ned ! 😃

    wenn delete auf einen Zeiger angewendet wird, dessen Pointeetyp unvollständig definiert ist?

    unvollstaendig definiert ? Der Code der den destruktor des auto_ptr baut, wird doch an der stelle generiert, wo die vollstaendige definition der klasse verfuegbar ist, bzw sein muss ???
    Das heisst doch, der destruktor code vom autoptr wird durch die cpp der Proxy Klasse generiert. Dort muss der Dtor der IMPL Klasse eben doch vollstaendig definiert sein, also auch der richtige DTor ???
    Waer an der Impl Klasse irgendwas unvollstaendig ... wuerde er den autoptr doch gar ned bauen koennen ???

    Hasst du mal nen beispiel fuer ?

    Ciao ...


  • Mod

    RHBaum schrieb:

    @camper

    Ich verstehs immer noch ned ! 😃

    wenn delete auf einen Zeiger angewendet wird, dessen Pointeetyp unvollständig definiert ist?

    unvollstaendig definiert ? Der Code der den destruktor des auto_ptr baut, wird doch an der stelle generiert, wo die vollstaendige definition der klasse verfuegbar ist, bzw sein muss ???
    Das heisst doch, der destruktor code vom autoptr wird durch die cpp der Proxy Klasse generiert. Dort muss der Dtor der IMPL Klasse eben doch vollstaendig definiert sein, also auch der richtige DTor ???
    Waer an der Impl Klasse irgendwas unvollstaendig ... wuerde er den autoptr doch gar ned bauen koennen ???

    Hasst du mal nen beispiel fuer ?

    Ciao ...

    Der Destruktor von auto_ptr wird in jeder Übersetzungseinheit instantiiert, die diesen aufruft (weil 1. Der Destruktor von auto_ptr dort definiert ist - Definition steht im Header <memory> - und 2. der Aufruf des Destruktors impliziert, dass der Destruktor irgendwo im Programm existiert). Ist der Destruktor der Schnittstellenklasse also der compilergenerierte, ist das jede ÜE, die diesen compilergenerierten Destruktor aufruft. Somit wird der Destruktor (von auto_ptr) ggf. in verschiedenen ÜEs instantiiert - und ggf. mit verschiedenem Inhalt. Schlimmer noch: wenn man nicht aufpasst, wird der Destruktor von auto_ptr in der ÜE, die die Impl-Implemention enthält, gar nicht instantiiert. Jedenfalls haben wir es dann mit Instantiierungen verschiedenen Inhalts zu tun und welche Version verwendet wird, ist mehr oder weniger Zufalls (abhängig von Linkeroptionen und inline-Substitution).
    delete kann auf einen Zeiger mit unvollständigem Typ angewendet werden, ruft dann aber nat. keinen Destruktor auf und benutzt den globalen delete-Operator.
    Hat der entsprechende Impl-Typ aber einen nicht-trivialen Destruktor, oder wurde der delete-Operator dafür überladen, ist das undefiniert.



  • Ahhh, ok, langsam komm ich glaub ich dahinter.

    Das Problem wuerde also massiv auftreten, wenn Du die auto_ptr mit ihrer Kopiersemantik nutzt, also die autoptr durchreichst, und dein autoptr irgendwo den scope komplett verliert ohne Zuweisung(also der destruktor aufgerufen wird) in einer ÜE, wo die vollstaendige definition des Pointers ned zur verfuegung steht ... also nur nen Forward vorhanden ist (und der compiler dann nen automatischen DTor geniert) richtig ???

    also bei einer Pimpl wuerde es kaum auftreten, da in der Proxyklasse die definition sowieso brauchst, man will ja da funktionen da aufrufen ???

    Das ist also eher nen Problem der Quelle Senke Thematik, wenn man die Erzeugung und das Zerstoeren in komplett anderen ÜE macht ?

    Die Frage für mich war, ob es sinn macht, den stl auto_ptr, den ich extensiv fuer die Pimpls verwendet habe, durch scoped ptr zu ersetzen, da wir vielleicht demnaechst endlich nen compiler mit tr1 impl bekommen (vc10).
    Denk mal ist eher Formsache, bei den standard pimpl isses wurscht, bei weitergabe (nicht const std::auto_ptr) sollt man vielleicht generell umschwenken. wuerd aber bedeiten, das andere mit älteren compilern die module nimmer bauen koennten ....oder nachtraeglich boost installieren muessten. Verwend den autoptr aber fast wirklich nur const als pimpl.

    Ciao ...


  • Mod

    RHBaum schrieb:

    Ahhh, ok, langsam komm ich glaub ich dahinter.

    Minimalbeispiel:

    //header
    #include <memory>
    struct Impl;
    struct Foo
    {
        void foo();
        std::auto_ptr<Impl> p;
    };
    // ÜE1
    #include "header"
    int main
    {
        Foo x;
    // hier wird Foo::~Foo aufgerufen.
    // Da das der vom Compiler deklarierte Destruktor ist,
    // wird er HIER definiert. Da dieser Destruktor selbst implizt
    // den Destruktor von p aufruft, muss hier ebenso der Destruktor
    // von auto_ptr<Impl> instantiiert werden, Impl ist an dieserr Stelle
    // unvollständig, also erfolgt kein Aufruf von ~ Impl und es wird der
    // globale delete-Operator benutzt
    // weil der vom Compiler generiert Destruktor inline ist, erfolgt
    // hier möglicherweise gar kein richtiger Funktionsaufruf
    }
    
    // ÜE2
    #include "header"
    struct Impl
    {
        void operator delete(void*) {}
        ~Impl() {}
    };
    void Foo::foo()
    {
    }
    // nichts in dieser ÜE bedingt die Instantiierung des Destruktors von Foo
    // oder std::auto_ptr<Impl>
    // selbst wenn das anders wäre, bleibt unbestimmt, welche Version am Ende
    // benutzt wird
    


  • camper schrieb:

    Es ist jetzt klar, dass Destruktor (und Copyctor/Copy-Zuweisung, falls benötigt) selbst implementiert werden müssen trotz der Verwendung von Smartpointern. Das ist in meinen Augen eine extrem negative Bilanz und nicht etwa ein "geringer Vorteil", weil es unnötig Aufmerksamkeit erfordert. An einem schlechten Tag hat man all diese Zusammenhänge auch schon mal wieder vergessen, schaut sich den Destruktor an, der scheinbar leer ist, und ersetzt ihn wieder durch den vom Compiler generierten (möglich jedenfalls bei auto_ptr oder smart_ptr ohne zu Compilerfehlern zu führen). Bei einem rohen Zeiger steht dort ein delete und es ist ganz klar, dass der Destruktor stehen bleiben muss.

    Die Gefahr sehe ich bei einem Destruktor mit delete genauso. Wenn jemand meint, meinen Code verbessern zu müssen und wenigstens etwas Skill hat, wird er den Destruktor zu entfernen versuchen und den Member durch einen smart pointer ersetzen. Dann kriegt er die Warnung (oder Fehler mit deinem scoped_ptr) und die Sache ist geklärt.

    Einen leeren Destruktor sieht man btw. ziemlich oft, zum Beispiel wenn man ihn virtual haben will (selbst abstrakte Destruktoren müssen definiert werden), sollte also niemanden übermäßig aufregen.

    Optimizer schrieb:

    Das ganze Idiom ist halt ne Krücke, wenn es jemand grundsätzlich ablehnt, verstehe ich das sogar. Ansonsten gehört es IMHO schon zum guten Ton, ein automatisches Objekt zu verwenden, außer es bereitet wirklich Extra-Aufwand.

    Automatische Objekte treten hier gar nicht auf... in jedem Fall ist die Automatik aber eine Illusion - und Code, der lügt, ist in jedem Fall abzulehnen. Soll der Smartpointer einem doch die Aufgabe abnehmen, über die Zerstörung zum richtigen Zeitpunkt nachdenken zu müssen, geht das in diesem Fall ja offensichtlich völlig fehl.

    Es geht doch gar nichts fehl. Der auto_ptr funktioniert genauso wie er soll, so wie er es immer gemacht hat, ich muss immer noch nicht über den Zeitpunkt der Zerstörung nachdenken und gelogen wird da auch nichts. Ziemlich harte Worte für perfekt funktionierenden Code. Deine Aussagen treffen nur zu, falls das delete im smart pointer nicht klappt, was man ja ohne großen Aufwand verhindern kann.

    Wenn du Panik hast, dass jemand den Destruktor entfernen könnte, kannst du ihn ja kommentieren oder den scoped_ptr verwenden. Schlechter als nen rohen Zeiger zu verwenden ist der Code mit dem smart_ptr sicherlich nicht. Ich hab ja schon eingeräumt, dass der Gewinn auch nicht so riesig ist.



  • Btw. der unique_ptr wird ja einige Schwachstellen vom auto_ptr ausmerzen, vielleicht wird da auch das delete-Problem angegangen.



  • Optimizer schrieb:

    Die Gefahr sehe ich bei einem Destruktor mit delete genauso. Wenn jemand meint, meinen Code verbessern zu müssen und wenigstens etwas Skill hat, wird er den Destruktor zu entfernen versuchen und den Member durch einen smart pointer ersetzen. Dann kriegt er die Warnung (oder Fehler mit deinem scoped_ptr) und die Sache ist geklärt.

    Oder er benutzt gerade einen Compiler/eine Warnstufe, mit der die Warnung nicht erkennbar ist, lässt die Warnung unter vielen unwichtigen untergehen oder ist einen Moment unachtsam und kompiliert danach nicht mehr. Selbst Menschen mit "Skill" passiert sowas dauernd wieder. Du solltest den Vorteil von Kompilierfehlern gegenüber eventuellen Warnungen nicht unterschätzen.

    Optimizer schrieb:

    Einen leeren Destruktor sieht man btw. ziemlich oft, zum Beispiel wenn man ihn virtual haben will (selbst abstrakte Destruktoren müssen definiert werden), sollte also niemanden übermäßig aufregen.

    Genau das wird man sich als Leser auch als erstes denken. Dann merkt man, dass nichts polymorph ist und überlegt sich vielleicht, dass der Destruktor mal benötigt wurde, aber inzwischen durch Änderungen in der Klasse redundant geworden ist. Es gibt sehr wenige Fälle, in denen eine leere Destruktorimplementierung nötig ist - es ist nicht einmal unwahrscheinlich, dass selbst ein guter Programmierer solche Situationen gar nicht kennt, da diese nie zuvor Probleme bereitet haben. Natürlich kann man sein Anliegen durch einen Kommentar verdeutlichen, aber dann hast du wieder etwas, das bei delete nicht nötig ist.

    Optimizer schrieb:

    Schlechter als nen rohen Zeiger zu verwenden ist der Code mit dem smart_ptr sicherlich nicht.

    Smart-Pointer haben zweifellos in sehr vielen Fällen Vorteile gegenüber rohen Zeigern. Dennoch würde ich nicht grundsätzlich davon ausgehen, dass sie nie schlechter sein können (unabhängig von dem Beispiel hier), besonders wenn keine klar überwiegenden Argumente existieren.



  • Nexus schrieb:

    Optimizer schrieb:

    Die Gefahr sehe ich bei einem Destruktor mit delete genauso. Wenn jemand meint, meinen Code verbessern zu müssen und wenigstens etwas Skill hat, wird er den Destruktor zu entfernen versuchen und den Member durch einen smart pointer ersetzen. Dann kriegt er die Warnung (oder Fehler mit deinem scoped_ptr) und die Sache ist geklärt.

    Oder er benutzt gerade einen Compiler/eine Warnstufe, mit der die Warnung nicht erkennbar ist, lässt die Warnung unter vielen unwichtigen untergehen oder ist einen Moment unachtsam und kompiliert danach nicht mehr. Selbst Menschen mit "Skill" passiert sowas dauernd wieder. Du solltest den Vorteil von Kompilierfehlern gegenüber eventuellen Warnungen nicht unterschätzen.

    Jo, ist einsichtig. Dann lass ich mich mal so weit breitschlagen, dass wir nur den scoped_ptr nehmen, der dieses Problem nicht hat (also eigentlich würd ich es ja für ne Schwäche der Sprache halten ;)).

    Natürlich kann man sein Anliegen durch einen Kommentar verdeutlichen, aber dann hast du wieder etwas, das bei delete nicht nötig ist.

    Doch, der Kommentar muss beim delete dann auch hin. Sonst bin ich nämlich der erste, der das delete entfernt und einen smart pointer einsetzt. Und der letzte bin ich nicht...

    Optimizer schrieb:

    Schlechter als nen rohen Zeiger zu verwenden ist der Code mit dem smart_ptr sicherlich nicht.

    Smart-Pointer haben zweifellos in sehr vielen Fällen Vorteile gegenüber rohen Zeigern. Dennoch würde ich nicht grundsätzlich davon ausgehen, dass sie nie schlechter sein können (unabhängig von dem Beispiel hier), besonders wenn keine klar überwiegenden Argumente existieren.

    In diesem Fall find ich den Code mit smart pointer nicht schlechter. Daher würde ich an dem Prinzip festhalten, smart pointer als Member zu benutzen, als default sozusagen.


Anmelden zum Antworten