PIMPL idiom und std::auto_ptr - Destruktor wird nicht aufgerufen



  • Obwohl's typischerweise funktioniert mit std::auto_ptr, wenn man den Destruktor der "äußeren" Klasse da definiert, wo TestImp vollständig bekannt ist, ist dies nicht vom Standard garantiert. Laut Standard lässt auto_ptr nur Ts zu, die vollständig sind.

    In C++0x wird es std::unique_ptr geben. Hier gibt es diese Bedingung nicht, jedenfalls dann nicht, wenn man einen eigenen "Deleter" verwendet. Also...

    class TestImpl; // wird woanders definiert
    
    struct TestImplDeleter {
      void operator()(TestImpl*) const; // wird woanders definiert
    };
    
    class Test
    {
      std::unique_ptr<TestImpl,TestImplDeleter> pimpl;
    public:
      ...
    };
    

    wäre völlig in Ordnung.



  • krümelkacker schrieb:

    In C++0x wird es std::unique_ptr geben. Hier gibt es diese Bedingung nicht, jedenfalls dann nicht, wenn man einen eigenen "Deleter" verwendet.

    Super. Einen Deleter zu implementieren macht auch weniger Arbeit als die Angabe eines leeren Destruktors.



  • War das Ernst oder Sarkasmus, Shlo?



  • Ist es eine rhetorische Frage?

    Wenn der OP meint, einen Destruktor mit einem leeren Body zu implementieren, nicht elegant sei, wird er wohl kaum jedes Mal einen Deleter schreiben wollen.



  • Shlo schrieb:

    Ist es eine rhetorische Frage?

    Nein, die Frage war ernst gemeint.

    Shlo schrieb:

    Wenn der OP meint, einen Destruktor mit einem leeren Body zu implementieren, nicht elegant sei, wird er wohl kaum jedes Mal einen Deleter schreiben wollen.

    Ich habe nicht den Eindruck, dass Du meinen Beitrag überhaupt richtig gelesen hast. Mir ging es um Korrektheit, nicht um Eleganz.



  • Erstmal herzlichen Dank für eure Antworten.

    Ich habe erstmal wie vorgeschlagen die Variante ausprobiert, einen Destruktor zu definieren (Dekl. in header, Definition in cpp).
    So wird zwar der Destruktor aufgerufen, der compiler (VS2010) spuckt allerdings trotzdem eine Warnung (C4150) aus:

    1>...pfad...\vc\include\memory(931): warning C4150: Löschen eines Zeigers auf den nicht definierten Typ 'TestImp'. Destruktor wurde nicht aufgerufen.
    1>          ...pfad...\test.h(4): Siehe Deklaration von 'TestImp'
    1>          ...pfad...\vc\include\memory(930): Bei der Kompilierung der  Klassen-template der std::auto_ptr<_Ty>::~auto_ptr(void)-Memberfunktion
    1>          with
    1>          [
    1>              _Ty=TestImp
    1>          ]
    1>          ...pfad...\test.h(18): Siehe Verweis auf die Instanziierung der gerade kompilierten Klassen-template "std::auto_ptr<_Ty>".
    1>          with
    1>          [
    1>              _Ty=TestImp
    1>          ]
    

    Danach hab ich krümelkackers Vorschlag umgesetzt und nutze nun einen unique_ptr, womit dies alles auch problemlos funktioniert.
    Soweit erstmal ganz nett.
    Nun stelle ich mir allerdings trotzdem die Frage, wie das beim shared_ptr umgesetzt wurde, da dort wohl kaum C++0x features zum Einstz kommen.
    Hat diesbezüglich jemand noch eine Erklärung für mich parat?

    Danke im Vorraus und Grüße



  • Argh, mal wieder Voraus falsch geschrieben 🙂

    Noch eine Ergänzung.
    Die Variante mit dem unique-pointer macht natürlich irgendwie recht wenig Sinn im Zusammenhang mit dem PIMPL-idiom.
    Wenn ich ohnehin extra einen deleter angeben muss und diesen dann folgendermassen in der cpp definiere:

    void TestImplDeleter::operator()(TestImp* p) const {
    	delete p;
    }
    

    dann könnte ich natürlich die ganze Sache mit dem smart-pointer auch vergessen und tatsächlich einfach einen Destruktor für Test angeben, der ein delete auf dem pointer aufruft.



  • krümelkacker schrieb:

    Mir ging es um Korrektheit, nicht um Eleganz.

    Verstehe. Deshalb schlägst du die Verwendung von Klassen eines noch nicht fertiggestellten Standards vor.

    pimplpompl schrieb:

    Erstmal herzlichen Dank für eure Antworten.

    Ich habe erstmal wie vorgeschlagen die Variante ausprobiert, einen Destruktor zu definieren (Dekl. in header, Definition in cpp).
    So wird zwar der Destruktor aufgerufen, der compiler (VS2010) spuckt allerdings trotzdem eine Warnung (C4150) aus:

    Irgendwas machst du falsch. Bei folgendem Code wird sogar mit /W4 keine Warnung ausgegeben.

    // .h
    #include <memory>
    
    class Test // : boost::noncopyable
    {
    public:
        Test();
        ~Test();
    
    private:
        class TestImp;
        std::auto_ptr<TestImp> imp;
    };
    
    // .cpp
    class Test::TestImp
    {
    public:
        ~TestImp()
        {
        }
    };
    
    Test::Test()
        : imp(new TestImp)
    {
    }
    
    Test::~Test()
    {
    }
    


  • Stimmt - so funktionierts und es gibt keine Warnung.

    Das Problem mit der Warnung war wohl, dass ich in der Definition von Test den privaten CCTOR mit leerem body angegeben habe.

    Definiere ich den CCTOR in der cpp nun folgendermaßen

    Test::Test(const Test& rhs) : imp(new TestImp) {}
    

    funktioniert es problemlos.

    Wie handhabt ihr denn das PIMPL-idiom? Mit smart-pointern, mit rohen pointern, ohnehin selten PIMPL, falls smart-pointer dann wie obiges Beispiel durch definieren eines ggf. leeren DTORs?



  • Shlo schrieb:

    krümelkacker schrieb:

    Mir ging es um Korrektheit, nicht um Eleganz.

    Verstehe. Deshalb schlägst du die Verwendung von Klassen eines noch nicht fertiggestellten Standards vor.

    Das, was Du als Vorschlag interpretiert hast, war als Zusatzinformation gedacht. Die Hauptbotschaft war: auto_ptr kann streng genommen (nach dem C++ Standard) nicht das, was hier gefordert wird -- mit oder ohne "Inline-Destruktor". Diese Botschaft ist sowohl richtig als auch on-topic, auch wenn es kein Lösungsvorschlag war. Nicht jeder Beitrag muss in einem Diskussionsforum einen Lösungsvorschlag enthalten. Wir sind hier nicht bei Stackoverflow. So, wie ich das sehe, bleibt eigentlich nichts außer dem unfreundlichen Ton von Deiner "Kritik" übrig.

    pimplpompl schrieb:

    ...
    dann könnte ich natürlich die ganze Sache mit dem smart-pointer auch vergessen und tatsächlich einfach einen Destruktor für Test angeben, der ein delete auf dem pointer aufruft.

    So richtig zufriedenstellend ist das alles nicht. Da stimme ich Dir zu.

    pimplpompl schrieb:

    Wie handhabt ihr denn das PIMPL-idiom? Mit smart-pointern, mit rohen pointern, ohnehin selten PIMPL, falls smart-pointer dann wie obiges Beispiel durch definieren eines ggf. leeren DTORs?

    Ich benutze dieses Muster nicht so oft. Die paar Male, wo ich es eingesetzt habe, habe ich es mit rohen Zeigern gemacht. In C++0x würde ich wahrscheinlich wirklich unique_ptr mit eigenem Deleter verwenden. Damit spart man sich auch mehr als einem benutzerdefinierten Destruktor. Move-Ctor und Move-Assignment bekommt man dann auch gratis vom Compiler dazu. Das wär's mir also wert.



  • krümelkacker schrieb:

    Das, was Du als Vorschlag interpretiert hast, war als Zusatzinformation gedacht. Die Hauptbotschaft war: auto_ptr kann streng genommen (nach dem C++ Standard) nicht das, was hier gefordert wird -- mit oder ohne "Inline-Destruktor".

    Jetzt machst du mich aber neugierig: Erkläre doch mal, was du im diesem Kontext unter streng verstehst. So, wie ich das sehe, steckt hinter dieser Aussage nichts, außer Wichtigtuerei.

    krümelkacker schrieb:

    So, wie ich das sehe, bleibt eigentlich nichts außer dem unfreundlichen Ton von Deiner "Kritik" übrig.

    Das siehst du falsch.



  • Shlo schrieb:

    Jetzt machst du mich aber neugierig:

    Hmm... Dann hat es ja doch etwas gebracht, dass ich mich 3mal wiederholt habe.

    Shlo schrieb:

    Erkläre doch mal, was du im diesem Kontext unter streng verstehst.

    Siehe ersten Abschnitt meines ersten Beitrags. Für den Fall, dass Du meinen Worten aus was für Gründen auch immer keinen Glauben schenken kannst, folgen entsprechende Zitate...

    ISO C++ Standard (2003) §17.4 Library-wide requirements

    In certain cases (..., operations on types used to instantiate library template components), the C++ Standard Library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.

    In particular, the effects are undefined in the following cases:

    • [...]
    • if an incomplete type is used as a template argument when instantiating a template component.

    Weiter macht der C++ Standard keine Ausnahme für auto_ptr.

    Der kommende C++ Standard enthält dagegen folgendes:

    n3225.pdf, §17.6 Library-wide requirements

    • [...]
    • if an incomplete type is used as a template argument when instantiating a template component unless specifically allowed for that component.

    n3225.pdf, §20.9.9 Class template unique_ptr

    ...The template parameter T of unique_ptr may be an incomplete type...

    Ja, das ist Korintenkackerrei, gerade weil es ja auch ohne diese Garantie mit auto_ptr zu funktionieren scheint (wenn man's "richtig" macht) ... aber streng genommen (nach ISO C++ Standard) ruft es undefiniertes Verhalten hervor. Ich halte es auf jeden Fall für erwähnenswert und ich verstehe nicht ganz, warum ich mich deswegen Dir gegenüber zig mal rechtfertigen soll.

    Shlo schrieb:

    So, wie ich das sehe, steckt hinter dieser Aussage nichts, außer Wichtigtuerei.

    krümelkacker schrieb:

    So, wie ich das sehe, bleibt eigentlich nichts außer dem unfreundlichen Ton von Deiner Kritik übrig.

    Das siehst du falsch.

    Let's agree to disagree.

    kk



  • Ich nehme solche Hinweise wie den von krümelkacker immer dankend an.
    Es gibt doch viele Beispiele von code, die eigentlich undefiniertes Verhalten produzieren, in der Praxis aber dennoch (teilweise durch kleine tricks) funktionieren. Nichts desto trotz vermeidet man das doch lieber.

    Selbst wenn man es nicht vermeiden möchte (weil es ja anscheinend trotzdem funktioniert) ist es sicherlich hilfreich die Hintergründe zu kennen und genau zu wissen, was man da wie anstellt.

    Insofern kann ich krümelkacker nur danken für die Informationen bezüglich momentanem Standard und Ausblick auf Zukünftiges.
    So wurden in diesem thread nicht nur Lösungen präsentiert sondern auch Informationen bezüglich der Standardkonformität.
    Gerade solche Zusatzinformationen machen doch einen solchen thread auch für anderes Forenuser interessant.

    Grüsse.



  • krümelkacker schrieb:

    Ja, das ist Korintenkackerrei, gerade weil es ja auch ohne diese Garantie mit auto_ptr zu funktionieren scheint (wenn man's "richtig" macht) ... aber streng genommen (nach ISO C++ Standard) ruft es undefiniertes Verhalten hervor. Ich halte es auf jeden Fall für erwähnenswert und ich verstehe nicht ganz, warum ich mich deswegen Dir gegenüber zig mal rechtfertigen soll.

    Nun, auch wenn du die Aussage noch weitere zehnmal wiederholst, wird sie nicht richtig. Man sollte nämlich wissen, wann ein Typ unvollständig ist, bevor man die zitierten Stellen aus dem Standard ins Spiel bringt. Denn durch die Definition eines non-inline Destruktors in Test, ist TestImp bei Instantiierung von std::auto_ptr<TestImp> (genauer gesagt dessen Destruktors) nicht mehr unvollständig. Wozu würde man sonst einen Destruktor mit leerem Body definieren?

    Ob streng genommen oder nicht, lagst du daneben. Schade, dass du nicht selbst darauf gekommen bist.


  • Mod

    Shlo schrieb:

    krümelkacker schrieb:

    Ja, das ist Korintenkackerrei, gerade weil es ja auch ohne diese Garantie mit auto_ptr zu funktionieren scheint (wenn man's "richtig" macht) ... aber streng genommen (nach ISO C++ Standard) ruft es undefiniertes Verhalten hervor. Ich halte es auf jeden Fall für erwähnenswert und ich verstehe nicht ganz, warum ich mich deswegen Dir gegenüber zig mal rechtfertigen soll.

    Nun, auch wenn du die Aussage noch weitere zehnmal wiederholst, wird sie nicht richtig. Man sollte nämlich wissen, wann ein Typ unvollständig ist, bevor man die zitierten Stellen aus dem Standard ins Spiel bringt. Denn durch die Definition eines non-inline Destruktors in Test, ist TestImp bei Instantiierung von std::auto_ptr<TestImp> (genauer gesagt dessen Destruktors) nicht mehr unvollständig. Wozu würde man sonst einen Destruktor mit leerem Body definieren?

    Ob streng genommen oder nicht, lagst du daneben. Schade, dass du nicht selbst darauf gekommen bist.

    Unfug.



  • camper schrieb:

    Unfug.

    Was bist du für ein Vogel?


  • Mod

    Shlo schrieb:

    camper schrieb:

    Unfug.

    Was bist du für ein Vogel?

    Frechheit ersetzt keine fehlenden Argumente.



  • Shlo schrieb:

    Wozu würde man sonst einen Destruktor mit leerem Body definieren?

    Es gibt einen Unterschied zwischen dem, was der C++ Standard sagt, und dem, was eine Implementierung daraus macht. Du berufst Dich scheinbar auf Implementierungsdetails. Eine Implementierung könnte aber auch ganz anders aussehen, wie zB:

    template<class T>
    class auto_ptr {
      ...
      static_assert(__is_complete_type(T),"violated requirements, T is incomplete");
      ...
    };
    

    (entsprechende Compiler-Magie bzgl static_assert und __is_complete_type vorausgesetzt) Dies müsste nach meinem Verständnis standardkonform sein. Dein Ansatz würde dann aber leider nicht mehr kompilieren. In dem Fall könntest Du Dich auch nicht beim Compilerhersteller beschweren, da Dein Programm die "library requirements" verletzt und damit undefiniertes Verhalten hervorruft.

    Mit unique_ptr sollte der Trick bzgl Destruktor garantiert aus den Gründen, die Du genannt hast, funktionieren -- auch ohne eigenen Deleter. Wegen des benutzerdefinierten Dtors würden dann aber die Move-Operationen nicht mehr automatisch generiert. Die müsste man dann per =default;-Syntax explizit erzwingen.

    Edit: Ah, ich erinnere mich. Du bist der Typ, der mir vor einer Woche unterstellen wollte, Iteratoren nicht verstanden zu haben. Dein Gehabe fand ich da auch schon nervig.

    kk



  • Shlo schrieb:

    unfug

    Die instantiierung eines Templates ist etwas völlig anderes als die Instantiierung eines Objektes.



  • camper schrieb:

    Frechheit ersetzt keine fehlenden Argumente.

    ROFL. Das sagt der Richtige... 🙄


Anmelden zum Antworten