Map von Pointern auf Membervariablen



  • class Object {
    public:
        virtual void Assign( const Object& other ); // oder meinetwegen operator=
    };
    
    class Double : public Object {
    public:
        void Assign( const Object& other ) {
             assert( other.GetType().Name() == "Double" ); // Oder was du zu gedenken tust
             *this = static_cast< const Double& >( other ); // oder dynamic_cast, aber dann bräuchtest du die Typnamen auch gar nicht mitschleifen
        }
    };
    


  • Es funktioniert. Danke dir für die Lösung. War auch ne schwierige Geburt ;). Ist zwar nicht 100% optimal weil jetzt jede Klasse diese Assign-Funktion mitbekommt und ich diese für jede Klasse individuell anpassen muss. Aber anders wird es wohl nicht gehen. Der Tipp mit dem überschreiben des =-Operators ist auch gut.



  • template< typename T >
    void Assign( Object& lhs, const T& rhs )
    {
    	assert( lhs.GetType() == rhs.GetType() );
    	static_cast<T&>(lhs) = rhs;
    }
    

    Ginge auch 😉 Wir, und du selbst, wissen ja noch nicht, was du am Ende nun damit machen willst 😉



  • Das wäre praktisch. Leider scheint man in templates keine Funktionen aufrufen zu können als typename. Hätte mir sonst ne Menge Schreibarbeit abgenommen.



  • Polymorphie und Wertsemantik vertragen sich nicht. Es irgendwie doch hinfrickeln zu wollen, führt garantiert zu Problemen. Ich kenne keinen einzigen Fall, in dem virtuelle Zuweisungsoperatoren sinnvoll sind.

    Wenn man Deep-Copy-Semantik möchte, ohne den dynamischen Typen zu kennen, kann man z.B. das Clone-Idiom anwenden. Dann überschreibt man gleich den (Smart-)Pointer und nicht nur das Objekt, wodurch auch Slicing verunmöglicht wird.



  • Also ganz so generell würde ich das jetzt nicht ausdrücken. Ich hab ersteres Beispiel schon erfolgreich verwenden können. Das ist nichts, worauf man ein komplettes Projekt aufbauen würde und anschließend auf Typen nicht mehr achtet, aber hat halt seine Anwendungen, bei denen zB. das reine Klonen andere Nachteile hätte.
    Ein paar Begründungen wären auch ganz nett gewesen, auch wenn ich weiß, dass man da immer sehr ausholen muss 😉 Aber so kommt das halt so rüber wie die Bibel, bei der auch jeder halbwegs intelligente hinterher fragt: Warum nur?



  • Decimad schrieb:

    Also ganz so generell würde ich das jetzt nicht ausdrücken. Ich hab ersteres Beispiel schon erfolgreich verwenden können.

    Schon möglich.
    Aber würdest Du heute noch so agieren?



  • Ich stand in der Zwischenzeit nicht wieder vor selber Problematik, daher weiß ich da jetzt auf Anhieb keine Antwort drauf! Ich denke aber mal, damals war so der Zeitpunkt, als ich von der Welt des statisch kompilierten in die Welt des dynamisch konfigurier- und parametrierbaren gegangen bin, da schießt man halt manchmal auch über das Ziel hinaus (irgendwie habe ich mir das aber damals auch einfach selber so zusammengebastelt, ohne da aus 10 Foren Hilfe zu benötigen). Inzwischen würde ich wahrscheinlich konservativer rangehen. Dafür zermartere ich mir aber jetzt vor lauter "du sollst nicht"-Regeln desöftern mal den Kopf und statt sie zu ignorieren quäle ich mich dann rum, auch wenn es bloß auf ein hinreichend funktionierendes Ergebnis ankommt 😉



  • Decimad schrieb:

    Aber so kommt das halt so rüber wie die Bibel, bei der auch jeder halbwegs intelligente hinterher fragt: Warum nur?

    Slicing wurde ja in diesem Thread schon mindestens ein Dutzend Mal erwähnt. Das ist ein allgegenwärtiges Problem bei solchem Gefrickel. Dazu kommt der umgekehrte Fall Derived = Base , wo nur das halbe Objekt überschrieben wird und undefiniertes Verhalten entstehen kann.

    Zuweisungen kann man genau dann zuverlässig verwenden, wenn man statische Typen vor sich liegen hat. Sonst muss man doch wieder Fälle unterscheiden, was den ganzen Versuch, das mit virtual unter einen Hut zu bringen, nutzlos macht. In dem Fall, wo statische Typen involviert sind, ist allerdings normale Wertsemantik ausreichend, und man kann sich die Frickelei sparen.

    Aber das Thema ist nicht neu:

    volkard schrieb:

    Ich verstehe den Bedarf gar nicht. Typverändernde Zuweisungen mache ich nicht auf Objekten, zum Beispiel weil das eh zu selten klappt, sondern auf Basisklassenzeigern, und auf einmal ist es logisch, einfach, schnell und einfach.

    Nexus schrieb:

    Nebenbei: Ein virtueller Zuweisungsoperator ist ein wenig fragwürdig. Und zwar einfach, weil C++ kein Double-Dispatching kann und der rechte Operand somit als statischer Typ interpretiert wird, was bei polymorphem Code wie

    Base* a = ...;
    Base* b = ...;
    
    *a = *b;
    

    nur die Basisklassenversion Base::operator= in Betracht zieht.

    Alternativen sind oft auch semantisch sinnvoller: Entweder, man verbietet Wertsemantik komplett (durch privaten Kopierkonstruktor und Zuweisungsoperator), oder man lässt sie auf Objekten gleichen Typs (in der gleichen Hierarchiestufe) zu, wodurch man wiederum kein virtual benötigt.

    Nexus schrieb:

    Das Problem ist, dass Wertsemantik (und damit Dinge wie Kopierkonstruktor oder Zuweisungsoperator) nicht direkt polymorph implementiert werden kann, weil die Objekte oft inkompatibel sind und man Slicing oder sonstiges undefiniertes Verhalten leichtfertig in Kauf nimmt, wenn man versucht, gleich wie bei statischen Typen vorzugehen.

    Das heisst nicht, dass polymorphe Klassen generell keine Kopierkonstruktoren und Zuweisungsoperatoren haben dürfen, jedoch sollten diese nicht virtuell sein. Beim Konstruktor ist bereits durch die Sprache gegeben, dass der dynamische Typ des neuen Objekts bekannt sein muss, beim Zuweisungsoperator jedoch nicht, was zu solchen Experimenten verleitet. Wertsemantik ist sehr wohl möglich, aber nur auf gleicher Ebene und mit statischen Typen. Für "polymorphe Wertsemantik" stellen immer noch Smart-Pointer eine Möglichkeit dar, welche z.B. eine virtuelle Clone()-Funktion aufrufen und sicherstellen, dass kein Slicing zum Tragen kommt.

    Edit: Mehr Zitate gefunden



  • Zuweisungen kann man genau dann zuverlässig verwenden, wenn man statische Typen vor sich liegen hat. Sonst muss man doch wieder Fälle unterscheiden, was den ganzen Versuch, das mit virtual unter einen Hut zu bringen, nutzlos macht. In dem Fall, wo statische Typen involviert sind, ist allerdings normale Wertsemantik ausreichend, und man kann sich die Frickelei sparen.

    Aber das bedeutet doch auch dass unter diesen Bedingungen ein dynamisches Erzeugen von Objekten zur Laufzeit nicht möglich ist, oder?

    Was ist eigentlich von QVariant oder boost::any zu halten?
    http://www.heise.de/developer/artikel/Exkurs-Boost-Variant-versus-QVariant-992958.html
    Wäre soetwas wie

    std::map<std::string, QVariant*>
    oder
    std::map<std::string, boost::any*>
    

    besser als meine Implementierung?

    @Nexus
    Sehe ich das richtig, dass deiner Meinung nach Referenzen nach Möglichkeit immer vermieden werden sollten?



  • Student83 schrieb:

    Aber das bedeutet doch auch dass unter diesen Bedingungen ein dynamisches Erzeugen von Objekten zur Laufzeit nicht möglich ist, oder?

    Doch, das geht gut, ich sehe ehrlich gesagt den Zusammenhang nicht. Nur kannst du unterschiedliche Typen (die du zur Kompilierzeit nicht kennst) einander nicht sinnvoll zuweisen. Aber mit einer Indirektion (Smart-Pointer) geht auch das gut.

    Student83 schrieb:

    Sehe ich das richtig, dass deiner Meinung nach Referenzen nach Möglichkeit immer vermieden werden sollten?

    Nein, natürlich nicht. Auch hier ist mir deine Schlussfolgerung ein Rätsel...

    Student83 schrieb:

    Was ist eigentlich von QVariant oder boost::any zu halten?

    Bisher habe ich noch keines wirklich gebraucht. Es gibt sicher sinnvolle Fälle, allerdings sind die wohl recht selten. Die Möglichkeit, alles in einen Container zu stopfen, verleitet schnell zu schlechtem Design, da man das Typsystem damit stark auflockert. Boost.Variant ist allerdings weniger kritisch als Boost.Any, da die Menge möglicher Typen vorgegeben ist. Mit dem Visitor-Pattern kann man die Elemente auch elegant besuchen.

    Ich würde allerdings zuerst nach einem konventionellen Lösungsansatz suchen, denn die genannten Massencontainer sind für vereinzelte Spezialfälle konzipiert.



  • Student83 schrieb:

    Was ist eigentlich von QVariant oder boost::any zu halten?

    Habe ich noch nie gebraucht.
    Könnte sein, daß man sowas praktisch nur braucht, um den Inner-Platform Effect intensiv ausleben zu können.



  • @nexus
    Kannst du mal einen Beispielcode geben bzw. grob skizzieren wie du z.B. den Zugriff auf die Membervariablen einer Klasse implementieren würdest.



  • Und Gott sprach: "Es werde ein Anti-Inner-Plattform-Fanatiker".
    Und es ward ein Anti-Inner-Plattform-Fanatiker.



  • Bevor ich nochmals alles aus diesem Thread wiederhole: Was willst du überhaupt erreichen? Wieso Strings auf Member mappen? Brauchst du das zur Serialisierung?



  • Also ich habe verschiedene Klassen mit unterschiedlichen Membervariablen. Beim Programmstart also zur Laufzeit wird nun eine Datei eingelesen. Damit bekomme ich Variablennamen und die zugehörigen Werte (beides als String) geliefert.
    Das macht Probleme:
    - Wie ordne ich dem Variablennamenstring die zugehörige Variable zu?
    Meine Lösung (siehe dieser Thread): Eine Map in der ich einen Zeiger auf jede Variable mit dem Namen der Variable als Key speichere.

    - Wie überliefere ich der Variablen (über den Zeiger) einen Wert?
    Meine Lösung (siehe dieser Thread): virtuelle Assign Funktion

    - Wie bekomme ich den Typ dieser Variablen?
    Meine Lösung: Alle Klassen bekommen im Grunde einen statischen String mit ihrem Namen verpasst

    - Wie erzeuge ich nun ein Objekt aus diesen Informationen?
    Meine Lösung: Factory Klasse wo alle Klassen registriert werden

    Und noch ganz wichtig. Die Library soll erweiterbar sein. D.h. ich muss auch Objekte erzeugen können, die ein Nutzer meiner Lib registriert hat.



  • Ah okay, das ist eine gute Beschreibung 🙂
    Im Prinzip ist es diese Problematik, oder?

    Man könnte sowas machen:

    struct MyClass
    {
        Int i;    // Alle
        Double d; // abgeleitet
        String s; // von Object
    
        std::map<const char*, Object*> catalog;
    
        // Wird mit getrennten Strings aus Datei gefüttert
        void WriteValue(const std::string& var, const std::string& value)
        {
            // z.B. var = "i", value = "37"
            auto itr = catalog.find(var.c_str());
            if (itr == catalog.end())
                throw std::runtime_error("Variable nicht vorhanden");
            itr->Write(value);
        }
    }
    

    Und eine solche Klasse könnte so aussehen:

    class Int : public Object
    {
        public:
            virtual void Write(const std::string& s)
            {
               std::istringstream stream(s);
               stream >> i;
               // Bei fail() oder noch weiteren Zeichen Exception
            }
    
        private:
            int i;
    }
    

    Und zum Auslesen könntest du das Umgekehrte machen:

    virtual std::string Read() const;
    


  • Ok, das habe ich ja sogar so implementiert, komme damit auch fast überall zurecht. Aber was wenn ich anstatt eines Strings doch ein Objekt gleichen Typs zuordnen möchte?

    Wenn man Deep-Copy-Semantik möchte, ohne den dynamischen Typen zu kennen, kann man z.B. das Clone-Idiom anwenden.
    Dann überschreibt man gleich den (Smart-)Pointer und nicht nur das Objekt, wodurch auch Slicing verunmöglicht wird.
    

    Meist du wirklich Clone-Idiom oder die Virtual-Constructor-Methode? Ich habe also mal der Klasse Object die virtuelle Funktion Get hinzugefügt und entsprechend in der Double Klasse überschrieben. Das soll ja gehen solange man nur den Rückgabewert verändert.

    Double* Double::Get()
    {
    	return this;
    }
    

    Und hier wieder der Testcode:

    Double d = 320;
    Object* e = &d;
    
    std::wcout << d << std::endl;
    *e->Get() = Double(640);
    std::wcout << d << std::endl;
    

    d ist aber trotzdem noch 320. So geht es also nicht. Kannst du mir evtl. nochmal ein Codebeispiel geben wie du die Implementierung gemeint hast?



  • Student83 schrieb:

    Ok, das habe ich ja sogar so implementiert, komme damit auch fast überall zurecht. Aber was wenn ich anstatt eines Strings doch ein Objekt gleichen Typs zuordnen möchte?

    Das kannst du ja immer noch direkt an den Integer . Oder was für eine Situation gibt es, in der du nur das abstrakte Object kennst und diesem einen int übergeben willst?

    Student83 schrieb:

    Meist du wirklich Clone-Idiom oder die Virtual-Constructor-Methode?

    Ich meinte schon Clone. Aber du musst unterscheiden zwischen Umwandlung/Parsen ("37" -> Integer(37)) und Kopieren (Integer(37) -> Integer(37)). Schau dir am besten das Clone-Idiom irgendwo an, bei Wikibooks oder so.

    Student83 schrieb:

    Ich habe also mal der Klasse Object die virtuelle Funktion Get hinzugefügt und entsprechend in der Double Klasse überschrieben. Das soll ja gehen solange man nur den Rückgabewert verändert.

    Ja, aber mit deinem return this; baust du nur den Adressoperator nach.

    Wenn du dir sicher bist, dass es ein Double ist, dann caste eben.

    Object* e = new Double(1.5);
    static_cast<Double*>(e)->SetValue(3.24);
    delete e;
    

    Und wenn du nicht sicher bist, benutze die genannten Abstraktionsmechanismen. Wenn sich diese nicht anwenden lassen, ist dein Design wahrscheinlich nicht ideal.


Anmelden zum Antworten