Operatoren für eigene Double Klasse



  • 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';
    }
    


  • Kann es sein dass wir gerade aneinander vorbeigeredet haben. Also bei mir ist jede Klasse im Projekt von der Klasse "Object" abgeleitet. Ist

    (Double)obj
    

    dann überhaupt eine explizite Typumwandlung da ja Double von Object abgeleitet ist?
    Ist auch eigentlich egal. Eignetlich wollte ich ja wissen welche Operatoren ich der Klasse im ersten Post noch hinzufügen/entfernen muss damit die Klasse mit dem Datentyp "double" zusammenspielt.



  • So ich habs. Ich musste einige Operatoren rausschmeißen und den Operator

    operator double() const;
    

    hinzufügen. Jetzt funktioniert es so wie ich es mir vorgestellt habe. Ob die Rechenoperationen auch richtig sind habe ich allerdings noch nicht getestet ;).

    class Double : public Object
    {
    public:
    	Double();
    	Double(double value);
    	//Double(float value);
    	//Double(int value);
    
    private:
    	double value;
    
    public:
    	//friend bool operator==(Double const& lhs, Double const& rhs);
    	//friend bool operator<(Double const& lhs, Double const& rhs);
    	Double& operator=(const Double& rhs);
    	Double& operator++();
    	const Double operator++(int);
    	Double& operator--();
    	const Double operator--(int);
    	Double& operator+=(Double const& rhs);
    	Double& operator-=(Double const& rhs);
    	Double& operator*=(Double const& rhs);
    	Double& operator/=(Double const& rhs);
    
    	operator double() const;
            /*operator float() const;
    	operator int() const;*/
    };
    

    Erstaunlich das die ganzen Operatoren nicht notwendig waren:

    /*const Double operator+(Double const& lhs, Double const& rhs);
    const Double operator-(Double const& lhs, Double const& rhs);
    const Double operator*(Double const& lhs, Double const& rhs);
    const Double operator/(Double const& lhs, Double const& rhs);
    bool operator!=(Double const& lhs, Double const& rhs);
    bool operator<(Double const& lhs, Double const& rhs);
    bool operator>(Double const& lhs, Double const& rhs);
    bool operator<=(Double const& lhs, Double const& rhs);
    bool operator>=(Double const& lhs, Double const& rhs);*/
    

    Was ich jetzt aber noch gerne wüsste ist, ob diese Klasse eurer Meinung nach noch verbesserungswürdig ist und ob die folgende Umwandlung nun implizit oder explizit ist?

    class Double : public Object {...}
    Double *d = new Double();
    Object *obj = d; //Implizit oder explizit?
    


  • Kann evtl. ein Moderator die hier entstandene Diskussion abtrennen. Meine Fragen gehen ganz unter ;).



  • Zu dir, Student83:

    • const Double würde ich nicht als Rückgabetyp verwenden. Double reicht. Denn (a + b) = c; ist kein Fehler, der immer wieder passiert und verboten gehört. Auch wenn es immer noch Leute gibt, die so argumentieren.
    • Wenn deine Klasse nur einen einzigen double enthält, würde ich sie jeweils als Kopie und nicht als Referenz auf const übergeben.

    Und warum sind die globalen Operatoren nicht nötig?

    Bei diesem Beispiel

    Double *d = new Double(); 
    Object *obj = d;
    

    führst du mit der zweiten Zeile einen impliziten Upcast von Double zur Basisklasse Object durch. Upcasts (in Richtung Basisklasse) sind immer implizit möglich (ausser bei Mehrdeutigkeiten durch Diamonds of Death, aber das ist hier unwichtig).



  • Nexus schrieb:

    Zu dir, Student83:
    [list][*] const Double würde ich nicht als Rückgabetyp verwenden. Double reicht. Denn (a + b) = c; ist kein Fehler, der immer wieder passiert und verboten gehört. Auch wenn es immer noch Leute gibt, die so argumentieren.

    Die Operatoren hatte ich so mehr oder weniger alle von hier genommen:
    www.c-plusplus.net/forum/232010
    und das const einfach übernommen. Das mit dem const ist mir sowieso noch ein Rätsel. Habe da schon zig unterschiedliche Versionen gesehen:

    const Double operator/(Double const& lhs, Double const& rhs);
          Double operator/(Double const& lhs, Double const& rhs) const;
    const Double operator/(const Double& lhs, const Double& rhs);
    

    Gibt es da eine Regel wo das const denn nun hingeschrieben wird?

    Nexus schrieb:

    Und warum sind die globalen Operatoren nicht nötig?

    Das wüsste ich auch gern. Zumindest funktioniert es jetzt tadellos. Klammere ich die Operatoren wieder aus bekomme ich Fehler wie:
    "error C2666: '::operator -' : 2 overloads have similar conversions"

    Noch eine weitere Frage. Und zwar habe ich den "="-Operator folgendermaßen implementiert:

    void swap(Double& a, Double& b)
    {
    	Double c(a); a=b; b=c;
    }
    
    Double& Double::operator=(const Double& rhs)
    {
    	if (this != &rhs)
    	{
    		Double tmp(rhs);
    		swap(*this, tmp);
    	}
    	return *this;
    }
    

    Was dazu führt dass sobald ich einem Double einen Wert zuweise (z.B. Double d = 42) sich das Programm ohne Fehlermeldung einfach beendet. Der Fehler liegt wohl an der swap-Funktion. Klammere ich diese aus geht es. Auch mit std::swap(*this, tmp); beendet sich das Programm automatisch.

    Und warum schreibt man eigentlich nicht stattdessen nur:

    Double& Double::operator=(const Double& rhs)
    {
    	value = rhs.value;
    	return *this;
    }
    

    So brauche ich keine temporäre Variable der ich noch den Wert von rhs zuweisen muss. Und Swappen brauche ich auch nicht?

    Und noch ein Hinweis an die Moderatoren. Bitte verlängert doch mal die Zeit nach der man hier automatisch abgemeldet wird. Ich habe es noch nicht geschafft einen Beitrag zu schreiben ohne dass ich mich neu einloggen musste ;).


Anmelden zum Antworten