this-Zeiger vom Besitzer der Cheshire Cat (Opaque Pointer, pImpl)



  • Neues Frage, neues Glück. Wenn ich mir einen opaquen Pointer innerhalb meiner Klasse angelegt habe, gibt es dann eine Möglichkeit, von einer der Methoden von CheshireCat auf den this-Pointer von PUBLIC zuzugreifen? Also wie müsste check () aussehen, wenn ich den aktuellen a-Wert mit dem aktuellen b-Wert vergleichen möchte?

    class PUBLIC {
    public:
        int a;
        auto setB (int value) { cc->b = value; );
    
    private:
        struct CheshireCat;
        CheshireCat *cc;
    }
    
    struct CheshireCat {
        int b;
    
        auto check () { /* ????? */ };
    };
    


  • Du kannst doch auf check eh nicht von außerhalb zugreifen. Höchstens in einer Methode von PUBLIC ... In dem Fall würde ich das auf die Schnelle wie folgt machen:

    class PUBLIC {
    public:
        int a;
        auto setB (int value) { cc->b = value; );
    
        bool doSomething () {
            if (cc->check (this) == true) {
                //do something
            }
            else {
                //do something else
            }
        }
    
    private:
        struct CheshireCat;
        CheshireCat *cc;
    }
    
    struct CheshireCat {
        int b;
    
        auto check (PUBLIC *p) {
            if (p->a == this->b)
                return true;
            else
                return false;
        };
    };
    

  • Mod

    Gib der CheshireCat doch einen Zeiger auf PUBLIC mit.



  • Fake oder Echt schrieb:

    Du kannst doch auf check eh nicht von außerhalb zugreifen. Höchstens in einer Methode von PUBLIC ... In dem Fall würde ich das auf die Schnelle wie folgt machen:

    class PUBLIC {
    public:
        int a;
        auto setB (int value) { cc->b = value; );
    
        bool doSomething () {
            if (cc->check (this) == true) {
                //do something
            }
            else {
                //do something else
            }
        }
     
    private:
        struct CheshireCat;
        CheshireCat *cc;
    }
     
    struct CheshireCat {
        int b;
     
        auto check (PUBLIC *p) {
            if (p->a == this->b)
                return true;
            else
                return false;
        };
    };
    

    Da müsste ich dann fast alle meine Methoden aus CheshireCat ändern, aber es scheint zumindest zu funktionieren (bei Minimal-Code von meinem Projekt hats geklappt).

    SeppJ schrieb:

    Gib der CheshireCat doch einen Zeiger auf PUBLIC mit.

    Meinst du denn das gleiche wie oben? Oder eher so:

    class PUBLIC {
    public:
    	int a;
    	PUBLIC ();
    
    private:
    	class CheshireCat;
    	CheshireCat *cc;
    };
    
    class PUBLIC::CheshireCat {
    public:
    	int b;
    	PUBLIC *ownerThis;
    
    	bool check () {
    		if (ownerThis->a == this->b)
    			return true;
    		else
    			return false;
    	};
    };
    
    PUBLIC::PUBLIC () {
    	this->cc = new CheshireCat;
    	this->cc->ownerThis = this;
    }
    

    Das habe ich gerade getestet, funktioniert auch (Destruktor habe ich hier jetzt weg gelassen). Mir gefallen an sich beide Lösungen. Bei der ersten brauche ich mich nicht um einen extra Pointer kümmern und bei der zweiten muss ich wiederum beim Methoden-Aufruf nichts ändern. Ich denke, es wird die zweite, das erspart etwas Arbeit.


  • Mod

    Ich meine die zweite Methode, aber bitte korrekt implementiert. Oder besser: Die Standardbibliothek nutzen, wenn man keine korrekte Implementierung hin bekommt. unique_ptr böte sich an.

    Ich muss sagen, ich bin etwas verwirrt. Hast du denn noch nicht nach Pimpl gegoogelt? All diese Implementierungsdetails, nach denen du fragst seit du das Stichwort bekommen hast, stehen doch detailliert in den Top-Treffern erklärt.



  • Also ich habe mich hiernach gerichtet:

    Put all the private member variables into a struct.
    Put the struct definition in the .cpp file.
    In the header file, put only the ForwardDeclaration of the struct.
    In the class definition, declare a (smart) pointer to the struct as the only private member variable.
    The constructors for the class need to create the struct.
    The destructor of the class needs to destroy the struct (possibly implicitly due to use of a smart pointer).
    The assignment operator and CopyConstructor need to copy the struct appropriately or else be disabled.
    

    Das ist direkt auf der ersten Seite zu finden, wenn man einfach nur pimpl bei Google eingibt. Da ich (meiner Meinung nach) das auch so implementiert habe (bis auf den Destruktor, der CheshireCat auch wieder frei gibt, aber das habe ich ja auch geschrieben, dass ich den nur für das Beispiel weggelassen habe), weiß ich nicht so recht, was eben nicht richtig an meiner Implementierung ist. Und das mit den Zeiger auf die echte Klasse (im letzten Beispiel PUBLIC) stand da auch nicht. Mit etwas überlegen wäre ich sicherlich auch selbst drauf gekommen, aber ich dachte mir, bevor ich irgendeinen Quark mache, frage ich nochmal nach.
    Das davor (also im anderen Thread) war nunmal ein Denkfehler, das kann denke ich mal passieren.


  • Mod

    Du hast das wichtige Wort "smart" überlesen. Dein Pointer ist nicht besonders smart und deiner Implementierung fehlt noch wesentlich mehr als nur ein Destruktor, um deinem Pointer die nötige Smartness zu verpassen.



  • Die Diskussionsfrage habe ich dazu auch im anderen Thread aufgeworfen. Warum muss denn der Pointer smart sein? Und was macht denn das "smart" aus?



  • Private schrieb:

    Die Diskussionsfrage habe ich dazu auch im anderen Thread aufgeworfen. Warum muss denn der Pointer smart sein? Und was macht denn das "smart" aus?

    Dann schau mal genau in dem anderen Thread.

    Die Frage nach "callback an das pimpl-objekt übergeben" vs. "callback beim Funktionsaufruf übergeben" beschreibt Herb Sutter ebenfalls in dem Artikel, den ich Dir verlinkt habe.



  • Das war nicht mehr die Frage. Die Frage zum "Callback" ist bereits beantwortet, es bleibt nur noch die Frage, was die "Smartness" meiner beschriebenen Variante von der Variante mit unique_ptr unterscheidet.



  • Private schrieb:

    Das war nicht mehr die Frage. Die Frage zum "Callback" ist bereits beantwortet, es bleibt nur noch die Frage, was die "Smartness" meiner beschriebenen Variante von der Variante mit unique_ptr unterscheidet.

    std::unique_ptr erledigt automatisch das, was du manuell machst. In anderen Situationen sogar das, was du gar nicht manuell machen kannst (bzgl. Exceptionsicherheit). Es gibt keinerlei guten Argumente, delete jemals in Anwendungscode zu verwenden (Ausnahmesituationen gibt es, aber eine solche liegt hier nicht vor). Wieso stellst du dich auf stur mit so stichhaltigen Argumenten wie "seh keine Vorteile außer dass es einfacher ist"? Wieso programmierst du nicht gleich alles in Assembler, gibt ja schließlich keine Nachteile, außer "dass es in C++ einfacher ist".

    Mit der Einstellung wirst du nie ein besserer Entwickler. Willst du echt zu den paar Spezis gehören, die selbst nach 15 Jahren Berufserfahrung Code schreiben, der unsicher, nicht portabel, instabil, langsam und dreimal so kompliziert wie nötig ist?



  • ;qwerty schrieb:

    Private schrieb:

    Das war nicht mehr die Frage. Die Frage zum "Callback" ist bereits beantwortet, es bleibt nur noch die Frage, was die "Smartness" meiner beschriebenen Variante von der Variante mit unique_ptr unterscheidet.

    std::unique_ptr erledigt automatisch das, was du manuell machst. In anderen Situationen sogar das, was du gar nicht manuell machen kannst (bzgl. Exceptionsicherheit). Es gibt keinerlei guten Argumente, delete jemals in Anwendungscode zu verwenden (Ausnahmesituationen gibt es, aber eine solche liegt hier nicht vor). Wieso stellst du dich auf stur mit so stichhaltigen Argumenten wie "seh keine Vorteile außer dass es einfacher ist"? Wieso programmierst du nicht gleich alles in Assembler, gibt ja schließlich keine Nachteile, außer "dass es in C++ einfacher ist".

    Mit der Einstellung wirst du nie ein besserer Entwickler. Willst du echt zu den paar Spezis gehören, die selbst nach 15 Jahren Berufserfahrung Code schreiben, der unsicher, nicht portabel, instabil, langsam und dreimal so kompliziert wie nötig ist?

    Meine Erfahrung mit <memory> tendieren derzeitig gegen Null. Es ist für mich also ungleich schwerer, unique_ptr anstatt new/delete zu nutzen. Ich habe bereits versucht, die Implementierung mit unique_ptr zu ersetzen, allerdings crashte mein Programm immer (prinzipiell funktionierte die Implementierung, jedoch habe ich es nicht hinbekommen, einen einwandfrei funktionierenden Copy-Konstruktor und -Operator umzusetzen). Daher frage ich mich, warum ich mir diesen Aufwand antun soll, wenn das auch so funktioniert. Beruflich komme ich aus dem Bereich der Ausbildung und wenn ich da was gelernt habe, ist es, dass man bei Lernenden mit "Mach so, weil besser" nicht viel erreicht. Auch wenn vllt. das Ergebnis dann "richtiger" ist, so fehlt es immer noch am nachhaltigen Verständnis. Dass unique_ptr exceptionsicher ist, hatte ich sogar gelesen, allerdings nicht beachtet. Als Argument ist das schon nahezu ausreichend (schließlich würde meine Implementierung derzeitig crashen, wenn new/delete fehl schlügen).



  • Nein, da liegst du leider falsch.

    1. Deine Anwendungen müssen nicht zwangsläufig "crashen", wenn du new/delete falsch einsetzt. Ein doppeltes delete führt zu undefiniertem Verhalten, und das ist so ziemlich das Übelste, was deinem Programm passieren kann.

    2. Der Einarbeitungsaufwand ist nicht so groß, dass man ihn als Einwand gegen sauberes Programmieren aufführen kann. Die Zeit, die du jetzt in das Lernen eines vernünftigen Speichermanagements steckst sparst du später um ein Vielfaches wieder ein, wenn du nicht mit dem Debugger durch dein halbes Programm laufen musst, um Speicherlecks und undefiniertes Verhalten zu suchen.


  • Mod

    Ist dir denn wenigstens klar, wieso meine erste Reaktion zu deinem Code war, dass er falsch ist und ich damit nicht das fehlende delete meinte? Wenn du das nämlich nicht verstehst, dann mangelt es an grundlegendem Hintergrundwissen. Das wäre aber erst recht ein Grund, solche Dinge nicht selber zu machen, sondern fertige Lösungen zu bevorzugen.



  • SeppJ schrieb:

    Ist dir denn wenigstens klar, wieso meine erste Reaktion zu deinem Code war, dass er falsch ist und ich damit nicht das fehlende delete meinte? Wenn du das nämlich nicht verstehst, dann mangelt es an grundlegendem Hintergrundwissen. Das wäre aber erst recht ein Grund, solche Dinge nicht selber zu machen, sondern fertige Lösungen zu bevorzugen.

    Wenn sich das darauf bezogen hatte, dass ich keine Smart-Pointer verwende, dann ja. Ansonsten freue ich mich auch über eine Erklärung


  • Mod

    Was passiert denn, wenn ich so ein Objekt kopiere oder einem anderen zuweise, wenn man das so macht, wie du das programmiert hast?



  • So wie ich es oben gepostet habe? Prinzipiell wird der Standard-Copy-Konstruktor genutzt. Dieser wird dann den Pointer cc vom "alten" Objekt zum neuen kopieren. Ist also eine einfach Pointer-Kopie. Sollte das alte Objekt dann gelöscht werden, wird die Adresse im neuen Objekt ins nichts zeigen, wodurch undefiniertes Verhalten entsteht bzw. das Programm crasht, falls auf das cc vom neuen Objekt zugegriffen werden soll. Ähnlich bei der Zuweisung: weise ich dem neuen cc einen Wert zu bzw. ändere den, so wird auch der vom alten geändert.


  • Mod

    Und wie löst du dieses Problem?



  • Um beim Beispiel zu bleiben:

    class PUBLIC {
    public:
    	PUBLIC ();
    	PUBLIC (PUBLIC&);
    	PUBLIC& operator = (PUBLIC);
    
    	~PUBLIC ();
    
    	int get ();
    	void set (int);
    
    private:
    	struct CheshireCat;
    	CheshireCat *cc;
    };
    
    struct PUBLIC::CheshireCat {
    	int b;
    };
    
    int PUBLIC::get () {
    	return this->cc->b;
    }
    
    void PUBLIC::set (int value) {
    	this->cc->b = value;
    }
    
    PUBLIC::PUBLIC () : cc (new CheshireCat) {
    }
    
    PUBLIC::PUBLIC (PUBLIC& origin) : cc (new CheshireCat (*(origin.cc))) {
    }
    
    PUBLIC& PUBLIC::operator = (PUBLIC origin) {
    	delete this->cc;
    	this->cc = new CheshireCat (*(origin.cc));
    
    	return *this;
    }
    
    PUBLIC::~PUBLIC () {
    	delete this->cc;
    }
    


  • Hättest du std::unique_ptr verwendet, hätte das automatisch den Kopierkonstruktor und den assignment operator gelöscht, sowie einen geeigneten Move-Konstruktor und move assignment operator angelegt (die hier nach wie vor fehlen) - ohne auch nur eine einzige Zeile Code schreiben zu müssen.

    Schau dir außerdem nochmal an, wie die Signaturen des Kopierkonstruktors und des Zuweisungsoperators aussehen müssen.


Log in to reply