Operatoren für eigene Double Klasse



  • Hi,

    dir fehlt noch ein laufzeit cast operator,...
    Ferner würde ich auch noch zwo zusätzliche überladungen mit einbauen,...

    class Double:
    {
    //...
    public:
    //...
    operator double()  //cast to basic double type
    {
       return this->value;
    }
    
    operator std::string()  // cast to printable type
    {
       //init return value
       std::stringstream strRet;
       // cast
       strRet<<(double)this;
       //and return   
       return strRet.str();
    }
    
    Double(); //std ctor
    Double(const Double& DCpy)// cpy ctor
    {
     *this=DCpy;
    }
    
    //hier noch !!!
    Double(const double& dCpy)// cast cpy ctor
    {
     *this=dCpy;
    }
    
    //nun die zusätzlichen überladungen
    
    Double & operator =( const double & dCpy)
    {
       this->value=dCpy;
       return *this;
    };
    
    const Double & operator = (const double & dCpy) const
    {
     this->value=dCpy;
     return *this;
    };
    
    Double & operator = (const Double & DCpy)
    {
     //check for self assignment
     if(this==&DCpy) return *this;
    
     this->value=DCpy.value;
     return *this;
    };
    
    const Double & operator=(const Double &DCpy) const
    {
     //check for self assignment
     if(this==&DCpy) return *this;
    
     this->value=DCpy.value;
     return *this;
    }
    };
    

    (Der Code is jetzt net getestet.... daher kleene fehler vorbehalten)

    greetz



  • BigNeal schrieb:

    halt den operator für die entsprechenden klassen.
    zB

    const Double operator+(Double const& lhs, double const& rhs);

    Nein, das ist nicht mehr nötig, da double implizit in Double konvertierbar ist, da der einparametrige Konstruktor nicht als explicit deklariert wurde.

    Was allerdings noch fehlt, ist die implizite Konvertierung eines Double in einen double. Dafür brauchst du operator double() .



  • zeusosc schrieb:

    const Double & operator = (const double & dCpy) const
    {
     this->value=dCpy;
     return *this;
    };
    
    const Double & operator=(const Double &DCpy) const
    {
     //check for self assignment
     if(this==&DCpy) return *this;
    
     this->value=DCpy.value;
     return *this;
    }
    

    Das sind jetzt aber ein paar const zu viel 😉 Insgesamt weiß ich nicht, warum du so viele Methoden definierst. Es reicht auch sowas:

    class Double
    {
    	double value;
    
    public:
    	Double(double value_) : value(value_) {}
    	operator double() { return value; }
    };
    
    int main()
    {
    	Double d = 5.0;
    	Double e = d;
    	d = e;
    	d = 10;
    }
    


  • Michael E. schrieb:

    Das sind jetzt aber ein paar const zu viel 😉

    hehe,. jo kann sein is ja auch schon spät,.. 🙂



  • Michael E. schrieb:

    Was allerdings noch fehlt, ist die implizite Konvertierung eines Double in einen double.

    ...mit der der Vorteil einer Klasse gegenüber double (Typsicherheit) wieder stark relativiert wird. Oder wieso eine Klasse schreiben, die sich fast genau gleich wie der eingebaute Typ verhält?

    Übrigens: Der Self-Assignment-Check ist nicht besonders sinnvoll, da der Overhead für die If-Abfrage die extrem seltene Verwendung von Selbstzuweisung nicht rechtfertigt und auch ohne alles perfekt funktioniert. Und der Rückgabetyp sollte bei allen Zuweisungsoperatoren gleich sein.



  • Nexus schrieb:

    Oder wieso eine Klasse schreiben, die sich fast genau gleich wie der eingebaute Typ verhält?

    Schlimmer noch: wenn beide Konvertierungen (hin und zurück) möglich sind, wird sich ein double nicht mehr wie sonst verhalten - weil in den unmöglichsten Gelegenheiten der Compiler plötzlich auf die Idee kommen kann, ein double klammheimlich implizit hin und zurück zu konvertieren um Code zu kompilieren, der z.B. wegen eines Flüchtigkeitsfehlers nicht kompilieren sollte.



  • Nexus schrieb:

    Michael E. schrieb:

    Was allerdings noch fehlt, ist die implizite Konvertierung eines Double in einen double.

    ...mit der der Vorteil einer Klasse gegenüber double (Typsicherheit) wieder stark relativiert wird. Oder wieso eine Klasse schreiben, die sich fast genau gleich wie der eingebaute Typ verhält?

    Auf Grund des geposteten Codes und dem Wunsch nach der impliziten Konvertierung nach double (siehe den Code, bei dem der OP gerne die Compiler-Fehler eliminiert hätte), bin ich davon ausgegangen, dass der OP zu Übungszwecken wirklich nur eine Klassenversion von double bauen will, die sich genauso wie double verhält. Dass eine solche Klasse nicht viel Sinn macht, sollte klar sein.



  • Naja, wenn man etwas schon nachbaut, soll man es wenigstens besser machen 😉

    Implizite Konvertierungen können ziemlich verlockend (weil "praktisch") sein, aber verursachen ab und zu hinterhältige Probleme. Gerade wenn man sich noch nicht so mit Klassen auskennt, schadet so ein Hinweis sicher nicht 🙂



  • Jo Nexus,

    Der selfassignment test ist gerade mal 2 zeilen asm code. Also nicht gerade overhead. ECX ist der schon übergebene this ptr, und ebp die referenz addy, welche beide so oder so übergeben werden,...

    cmp ecx,ebp
     je SHORT _ende //<-- Double::'operator = (.,.)' + 0c3h oder so,...
    

    lieber vorher abchecken als im speziallfall abkÄcken 😉



  • zeusosc schrieb:

    lieber vorher abchecken als im speziallfall abkÄcken 😉

    Überleg doch mal, was bei einer Zuweisung eines double s schief gehen soll. Dann merkst du schnell, dass der Test auf Selbstzuweisung sinnlos ist, weil er dich vor gar nichts schützt, aber das Programm langsamer und den Code hässlicher macht. Um das zu erkennen, muss man nicht in den Assemblercode schauen. Wie viel es performancemässig tatsächlich ausmacht, ist komplett irrelevant. Da der Test nichts bringt, ist jede Nanosekunde eine zuviel.

    Überhaupt ist ein Test auf Selbstzuweisung ein starkes Anzeichen dafür, dass das Problem falsch angegangen wird. Sobald die Semantik nämlich komplexer wird und Exceptionsicherheit ins Spiel kommt, nützt dir das nicht mehr viel. Und für die einfachen Fälle nützt es erst recht nichts, weil Selbstzuweisung so gut wie nie vorkommt und kaum etwas schiefgehen kann.

    Um einen Zuweisungsoperator sinnvoll zu implementieren, benutze das Copy-and-Swap-Idiom. Bei trivialen Zuweisungen wie hier reicht der compilergenerierte Zuweisungsoperator längstens.



  • Mir wäre es natürlich auch lieber ich könnte einfach double verwenden. Aber ich brauche eine Funktion die verschiedene Datentypen annehmen kann. Das kann ein double, int, std::wstring, XmlNode oder was auch immer sein. Dazu müssen aber all diese Datentypen von einer gemeinsamen Klasse (z.B. Object) abgeleitet sein, damit ich z.B. folgendes machen kann:

    void Funktion(Object *obj)
    {
        //Hier soll geprüft werden, ob obj ein Double ist
        if ((Double)obj)
        {
            //...
        }
        //Hier soll geprüft werden, ob obj ein XmlNode ist
        if ((XmlNode)obj)
        {
            //...
        }
    }
    

    Das Problem hierbei ist, dass Object kein "normaler double" sein kein, weshalb ich mich gezwungen sehe eine eigene Double Klasse von Object abgeleitet zu implementieren.

    Beim erstellen der Klasse habe ich mich hieran orientiert:
    www.c-plusplus.net/forum/232010
    http://stackoverflow.com/questions/4421706/operator-overloading/4421708#4421708
    Allerdings bin ich nach euren Kommentaren nun völlig durcheinander. Gibt es irgendwo im Netz ein Musterbeispiel wie ich die Klasse implementieren soll? Oder könnt ihr mir mal auflisten welche Operatorn jetzt sinnvoll sind und welche nicht?

    einen Operator

    Double::operator double() const;
    

    bzw.

    Double::operator int() const;
    

    hatte ich auch schonmal implementiert, aber dadurch hat der Compiler nicht mehr gewusst welche Konvertierung er nun nehmen soll und Fehler ausgegeben. Ich würde nämlich gerne auch z.B. ints oder floats einem Double zuweisen können. Nicht aber umgekehrt.



  • Nexus schrieb:

    Um einen Zuweisungsoperator sinnvoll zu implementieren, benutze das Copy-and-Swap-Idiom.

    Ist richtig, so wirds ja auch in der STL gemacht ...

    Immerhin wird auch Beim Cpy-n-Swap-Idiom der STL ein assignment test gemacht ..

    void __CLR_OR_THIS_CALL swap(_Myt& _Right)
    		{	// exchange contents with _Right
    		if (this == &_Right)
    			;	// same object, do nothing
    		else if (_Mybase::_Alval == _Right._Alval)
    			{	// same allocator, swap control information
    

  • Mod

    @student83: Das ist ganz klar ein Fehler im Design. So etwas kann in keinem denkbaren Kontext sinnvoll vorkommen.



  • Ich denke eher, dass es sich bei der Dinkumware-Implementierung der STL um eine Optimierung handelt. Aber da anschliessend ohnehin Copy-and-Swap angewandt wird, würde es auch ohne funktionieren, soweit ich das sehe.

    Jedenfalls bleibt festzuhalten, dass ein Test auf Selbstzuweisung weder wie du (zeusosc) sagst aus Sicherheitsgründen nützlich ist, noch gibt es im Beispiel dieses Threads auch nur irgendeinen Grund dafür. Ganz allgemein fährt man sicher nicht schlecht, wenn man gänzlich auf diese Tests verzichtet. Zumal Selbstzuweisung wie schon gesagt sehr selten vorkommt.

    Und Student83, willst du da tatsächlich eine explizite Typunterscheidung durchführen? Genau für sowas gibts virtuelle Funktionen.



  • Dann sag das mal Microsoft ;).
    Das Problem ist wenn ich z.b. einen Style aus einer XAML Datei auswerte dann gibt es dort z.B. setter mit denen man Vaiablen setzen kann. Das kann die Width sein oder aber auch das ControlTemplate. Ich finde diese Lösung eigentlich ganz schön. Wenn ich es anders lösen muss wird es erst recht gruselig, mal davon abgesehen dass ich gar nicht weis wie ich es anders machen könnte.


  • Mod

    Student83 schrieb:

    Dann sag das mal Microsoft ;).

    Nein, das sag ich dir und deine Problembeschreibung bestätigt dies.

    mal davon abgesehen dass ich gar nicht weis wie ich es anders machen könnte.

    Dies wird's wohl sein. Virtuelle Funktionen wurden genannt, etwas anderes ist Überladung. Um zu entscheiden was geeignet ist, fehlt der Kontext. Aber Typprüfung zur Laufzeit, noch dazu ohne Polymorphie, ist ein ganz deutliches Alarmsignal dass man etwas falsch* macht.

    *: Falsch im Sinne von schlecht. Dein Programm mag durchaus so funktionieren, aber es ist nicht gut in fast jeder Hinsicht.



  • Das mit den virtuellen Funktionen ist ein gutes Stichwort, allerdings muss ich den Inhalt des

    <Setter Property="Irgendwas"><Setter.Value>...Inhalt...</Setter.Value></Setter>
    

    ja irgendwo im Speicher ablegen. Das ist die Variable

    Object value;
    

    in der Setter-Klasse.



  • Vor einiger Zeit habe ich ein paar Punkte genannt, warum explizite Typunterscheidungen schlecht sind:

    Nexus schrieb:

    Einerseits sind Typunterscheidungen sehr fehleranfällig und müssen ständig mit der Klassenhierarchie konsistent bleiben. Wenn du Klassen berarbeitest, brauchst du an einem anderen Ort nochmals Code zu ändern. Bei dynamic_cast hast du zudem das Problem, dass die Reihenfolge der if-else-Statements richtig sein muss (abgeleitete müssen vor Basisklassen stehen). Bei typeid tritt das zwar nicht auf, dafür muss der Typ exakt stimmen, du kannst also mit Basisklassenabfragen keine abgeleiteten Klassen einschliessen. Ein weiteres Problem, das die Zentralisierung des Verhaltens mit sich bringt, ist die starke Abhängigkeit von Code. Bei der Typabfrage müssen nämlich alle Klassen vollständig bekannt sein. Besonders bei grösseren Projekten kann das die Kompilierzeit massiv beeinträchtigen, im Falle mehrerer Typabfragen in unterschiedlichen Modulen umso mehr.

    Das Wichtigste ist aber wohl, dass explizite Typunterscheidungen den Grundsatz der Polymorphie verletzen. Polymorphie (nicht nur dynamische) erlaubt es einem, Objekte mit unterschiedlichem Verhalten einheitlich anzusprechen, was eine starke Abstraktion ermöglicht. Mit manuellen Fallunterscheidungen verwirft man dieses Konzept und geht einen Schritt zurück – weg von objektorientierter Programmierung, in der ein Objekt selbst für sich schaut.



  • Gut, aber was wäre denn dann die Alternative? Ich müsste für jede Variable die ein Setter setzen kann eine eigene Setter Klasse schreiben die dann den den entsprechenden Setter.value speichert. Also z.B.

    class IntSetter
    {
        int value;
    }
    
    class DoubleSetter
    {
        double value;
    }
    
    class ControlSetter
    {
        Control value;
    }
    
    //50 Steuerelemente später...
    
    class TabItemSetter
    {
        TabItem value;
    }
    

    Und dann stehe ich wieder vor dem selben Problem. Ich muss nämlich (explizit) feststellen von welchem Typ der Setter ist wenn ich ihn anwenden will also den Setter.value abrufen möchte.

    Was mir noch einfällt wäre ich gebe der Setter Klasse für jeden Datentyp einen Zeiger:

    class Setter
    {
        int *value;
        double *value;
        Control *value;
        //...
        TabItem *value;
    }
    

    Allerdings muss ich ja dann auch wieder eine Rückgabefunktion haben für jeden Zeiger. Den zurückgegebenen Zeiger prüfe ich dann, ob er ungleich null ist. Das ist einfach Wahnsinn. Das würde den Code enorm aufblähen.


  • Mod

    struct Foo
    {
      int a;
      double b;
      void set(double bb){b=bb;}
      void set(int aa){a=aa;}
    };
    
    int main()
    {
      Foo bar;
      bar.set(5);
      bar.set(3.124);
      cout<<bar.a<<' '<<bar.b<<'\n';
    }
    

Anmelden zum Antworten