Operatoren überladen (=)



  • Hallo.

    - UPDATE vom 05.05.04 -

    So funktioniert nun alles wie es in der Aufgabenstellung gegeben war.

    Habe mir wirklich jeden Beitrag von euch angeschaut. Leider verstehe ich noch nicht alles, weil ich einfach noch nicht so weit bin. Deswegen wird der Quellcode wohl auch noch nicht das "Optimum" sein. Bei Copy-Kontruktor und Operator überladen wurde ich auch einfach mal ins kalte Wasser geworfen.

    Aufgabenstellung grob zusammengefasst:

    Polynome werden in einem Array dagestellt. Z.B. 5x7-6x4+9x^2+5x-7 wird zu { 7,5,4,-6,2,9,1,5,0,-7,-1 } ... also { Potenz, Koeffizient, Protenz, Koeffizient, ..., -1 } ... -1 soll als Endemakierung dienen.

    protected:
    int *pol; // Attribut für den Zeiger auf das Polynom
    int Laenge(); // gibt die Anzahl der Int Werte in pol an

    public:
    POLY(); // setzt pol auf NULL
    POLY(int *p, int n); // Auf der Position p wird jeweils der Zeiger auf das zu bearbeitende Polynom übergeben, n gibt die Anzahl der x-Terme im Polynom an
    void ausgeben(); // Polynom in lesbarer Form ausgeben
    int Grad(); // ausgeben des Polynomgrades (höchste x-Potenz)
    ~POLY() { delete [] pol } // (Vorgabe)

    Ausserdem:
    Zuweisungsoperator "=" überladen, so das nicht nur eine "flache" Kopie gemacht wird. Flache Kopie: Das heisst im Beispiel das A.pol[i] und B.pol[i] nach B=A auf das gleiche Array zeigen. Das soll aber nicht sein. B soll ein eigenes Array bekommen mit den Werten des Arrays von A. Also eine "tiefe" Kopie.

    Zum testen der Überladung soll dann int *pol in public gesetzt werden, nach B=A in A.pol[0] ein beliebiger Wert gesetzt werden und dann eine Ausgabe zum vergleichen gemacht werden.

    Klasse Definition:

    class POLY {
    	protected:
    		int Laenge();
    		int *pol;
    	public:
    		POLY();
    		~POLY();
    		POLY(int *p, int n);
    		POLY &POLY::operator =(POLY &px);
    		void ausgeben();
    		int Grad();
    };
    

    Klasse Umsetzung:

    #include <iostream.h>
    #include "Poly.h"
    
    POLY::POLY()
    {
    	pol = NULL;
    }
    
    POLY::POLY(int *p, int n)
    {
    	int la = (n*2) + 1;
    
    	pol = new int[la];
    
    	for(int i=0; i<la; i++)
    	{
    		pol[i] = p[i];
    	}
    }
    
    POLY::~POLY()
    {
    	delete [] pol;
    }
    
    POLY &POLY::operator =(POLY &px)
    {
    	int la = px.Laenge();
    
    	pol = new int[la];
    
    	for(int i=0; i<la; i++)
    	{
    		pol[i] = px.pol[i];
    	}
    
    	return *this;
    }
    
    int POLY::Laenge()
    {
    	int i=0;
    	for(i=0; pol[i] != -1; i=i+2);
    	return (i+1);
    }
    
    void POLY::ausgeben()
    {
    	for(int i=1; i<Laenge(); i=i+2)
    	{
    		if(pol[i]>0)
    			cout<<"+"<<pol[i]<<"x^"<<pol[i-1];
    		else
    			cout<<pol[i]<<"x^"<<pol[i-1];
    	}
    	cout<<endl<<endl;
    }
    
    int POLY::Grad()
    {
    	return (pol[0]);
    }
    

    Main:

    #include <iostream.h>
    #include "Poly.h"
    
    int polynomeins[] = { 7,5,4,-6,2,9,1,5,0,-7,-1 };
    
    POLY A(polynomeins,5),B;
    
    int main()
    {
    	cout<<"Grad von A: "<<A.Grad()<<endl<<endl;
    	cout<<"Ausgabe des Polynoms von A: ";
    	A.ausgeben();
    
    	B = A;
    
    	cout<<"Grad von B: "<<B.Grad()<<endl<<endl;
    	cout<<"Ausgabe des Polynoms von B: ";
    	B.ausgeben();
    
    // Was zum testen ...
    
    // Nur wenn *pol in public steht. Ansonsten auskommentieren.
    
    // <anfang>
    /*
    	cout<<"Erster Eintrag in A wird geaendert was keine Auswirkung auf B haben sollte."<<endl<<endl;
    
    	A.pol[0] = 9;
    
    	cout<<"Grad von A: "<<A.Grad()<<endl<<endl;
    	cout<<"Ausgabe des Polynoms von A: ";
    	A.ausgeben();
    
    	cout<<"Grad von B: "<<B.Grad()<<endl<<endl;
    	cout<<"Ausgabe des Polynoms von B: ";
    	B.ausgeben();
    */
    // <ende>
    
    	return (0);
    }
    

    Gruß,
    Johrtreel



  • Ich habe mir deinen Code jetzt praktisch nicht angeschaut, weil mir sofort zwei gravierende Fehler aufgefallen sind.

    - Du lieferst im operator= eine Kopie des Objekts zurück! Gib ein const Referenz zurück. Das selbe für die Parameter!
    - Selbstzuweisung (a = a) unbedingt abfangen, sonst löscht du das alte und initialisierst dann wieder mit dem bereits gelöschten Wert. Einfach die Adressen vergleichen, falls &parameter == this, nichts machen.

    EDIT: Wenn ichs mir recht überlege, dürfte das sogar der Grund sein, weil du dein kopiertes Parameter-Objekt am Ende des Blocks vernichtest und damit ein zweites mal die referenzierten Daten löscht.



  • POLY POLY::operator =(POLY px) // Operator überladen 
    { 
            delete[] pol;         //Edit
            xe = px.xe;
            pol = new int[xe]; // dem Array eine Größe geben 
            for(i=0; i<xe; i++) // Daten von Quellarray in Zielarray 
            { 
                    pol[i] = px.pol[i]; 
            } 
    }
    

    ja und dann kommen noch die sachen rein die Optimizer gesagt hat...also selbsvergleich usw.



  • nur mal so ne gedankliche frage:
    sind selbstzuweisungen nicht design fehler?
    sollte man bei selbstzweisungen nicht vor return true ne exception werfen wie:

    throw selbstzuweisung(__FILE__,__LINE__);
    

    dann kann das abgefangen werden, und in eine logbuchdatei geschrieben...
    hat den vorteil, dass man solche designschnitzer erkennt, und die if abfrage schließlich im finalen code rausnehmen kann(sowas geht ja einfach mittels #define, muss man nur von anfang an mit coden).



  • Auauauauauauaua

    1. Logikfehler sind asserts
    2. warum sollte es ein logikfehler sein? vielleicht nehme ich eine moegliche selbstzuweisung in kauf, um den code sauber zu halten (es gibt ja referenzen die "verstecken" die urspruengliche variable ja)

    der op= ist eine katastrophe

    POLY const& POLY::operator =(POLY px) // Operator überladen 
    { 
      swap(px);
      return *this;
    }
    

    so implementiert man swap, welches die zeiger tauscht und laesst den copyctor die ganze arbeit uebernehmen.

    und eine kopierschleife verwenden wir auch _nie_ - es gibt std::copy



  • Shade Of Mine schrieb:

    1. warum sollte es ein logikfehler sein? vielleicht nehme ich eine moegliche selbstzuweisung in kauf, um den code sauber zu halten (es gibt ja referenzen die "verstecken" die urspruengliche variable ja)

    im fall von referenzen ist das eher schlampig,oder fällt dir eine situation ein, in der es vom programmiertechnischen her unmöglich ist zu verhindern, dass eine selbstzuweisung überhaupt stattfinden kann?(unter der vorraussetzung natürlich, dass man seinen code anständig kommentiert und sauber aufgebaut hat)

    desweiteren mach ich persönlich nen bogen um assert solange es kein fehler ist,der verhindert, dass mein programm noch weiterlaufen kann, im falle von selbstzuweisungen reicht auch ne exception(obwohl es ein logikfehler ist, die programmlogik ist halt fehlerhaft,da sie das nicht automatisch verhindern konnte).



  • ... und der übergebene Parameter sollte keine Kopie sein! Und der copy-ctor fehlt auch.

    class POLY
    {
        ...
        POLY(const POLY& rhs)
        {
            ...
        }
        POLY& operator=(const POLY& rhs)
        {
            ...
            return *this;
        }
        ...
    }
    

    Wenn man auf Selbstzuweisung aus einem anderen Grund prüfen muss, als sich überflüssige und möglicherweise kostspielige Anweisungen zu sparen, dann hat man oft ein anderes Problem: Der Code ist dann in vielen Fällen nicht Exception-sicher! Wenn man erst den Speicher löscht und new eine Exception auslöst, steht man dumm da - Der Zeiger zeigt jetzt auf ungültige Daten und spätestens im Konstruktor kracht's. Die Lösung mit swap ist besser 😉



  • Shade Of Mine schrieb:

    ... so implementiert man swap, welches die zeiger tauscht und laesst den copyctor die ganze arbeit uebernehmen.

    kannst mir mal erklären wie du das meinst mit swap? unter swap verstehe ich tauschen aber bei einer zuweisung geht es nicht um tauschen sondern um kopieren. rechtes Objekt übergibt Daten ans linke Objekt ohne selbst verändert zu werden.
    wo ist mein fehler?



  • Beim auto-ptr Smart-Pointer wird getauscht statt zugewiesen, ich finde die Überladung aber sowieso nicht sonderlich schön, weil der Parameter, wie schon gesagt, eine Referenz sein sollte, oder?



  • otze schrieb:

    im fall von referenzen ist das eher schlampig,oder fällt dir eine situation ein, in der es vom programmiertechnischen her unmöglich ist zu verhindern, dass eine selbstzuweisung überhaupt stattfinden kann?(unter der vorraussetzung natürlich, dass man seinen code anständig kommentiert und sauber aufgebaut hat)

    1. habe ich quasi nie kommentare im quellcode - wozu auch?
    2. programmiere ich sicher nicht schlampig
    3. man _kann_ es vermeiden, nur warum sollte ich es immer vermeiden wollen?
      wenn es einfacher ist diese selbstzuweisung uU in kauf zu nehmen als sie explizit abzufangen.
    4. if in doubt, do as the ints do

    desweiteren mach ich persönlich nen bogen um assert solange es kein fehler ist,der verhindert, dass mein programm noch weiterlaufen kann,

    Das ist ein fehler deinerseits.
    assert ist fuer _alle_ logikfehler.

    im falle von selbstzuweisungen reicht auch ne exception(obwohl es ein logikfehler ist, die programmlogik ist halt fehlerhaft,da sie das nicht automatisch verhindern konnte).

    eine exception wuerde heissen, dass du auf diese situation reagieren kannst: wie denn?



  • Hallo,

    Shade Of Mine schrieb:

    1. habe ich quasi nie kommentare im quellcode - wozu auch?

    vielleicht (nur) deshalb, damit auch andere Entwickler deinen Code leichter und schneller verstehen und sich in diesen einarbeiten können, falls der Code "in andere Hände" gerät?

    Oder um Tools einzusetzen, die aus Kommentaren Dokus erzeugen können ?

    Oder du hast einen so exzellenten coding style, der keinerlei Kommentare bedarf :), dann Glückwunsch.

    Ich gebe allerdings zu, daß auch bei mir Kommentare öfter zu kurz kommen, dies liegt aber eher an den in Projekten einzuhaltenden Terminen.

    Zum Thema: in den anderen Anmerkungen stimme ich überein, ein assert ist durchaus angebracht, auf das Werfen irgendwelcher exceptions würde ich hier (Selbstzuweisung) nicht einmal im Traum kommen (müßten schon sehr schlechte Träume sein 🙂 )

    MfG



  • Online schrieb:

    rechtes Objekt übergibt Daten ans linke Objekt ohne selbst verändert zu werden.
    wo ist mein fehler?

    genau das passiert doch mit shades op= 😕
    schau ihn dir nochmal genau an.



  • Probe-Nutzer schrieb:

    vielleicht (nur) deshalb, damit auch andere Entwickler deinen Code leichter und schneller verstehen und sich in diesen einarbeiten können, falls der Code "in andere Hände" gerät?

    Warum sollte ich zB folgendes kommentieren:

    template<class Policy>
    class Timer
    {
    public:
      typedef typename Policy::value_type value_type;
    private:
      value_type value;
    
    public:
      Timer()
      {
        Policy::now(value);
      }
    
      value_type elapsed() const
      {
        return Policy::elapsed(value);
      }
    };
    

    wo passen da kommentare hin?

    Oder um Tools einzusetzen, die aus Kommentaren Dokus erzeugen können ?

    das ist n gutes argument - brauch ich aber nur wenn ich eine library schreibe.

    Oder du hast einen so exzellenten coding style, der keinerlei Kommentare bedarf :), dann Glückwunsch.

    sobald ich einen kommentar brauche um den code zu verstehen, schreibe ich ihn um.

    in der arbeit geht das zwar nicht - aber dort gibt es auch keine doku oder aehnliches :p



  • davie schrieb:

    Online schrieb:

    rechtes Objekt übergibt Daten ans linke Objekt ohne selbst verändert zu werden.
    wo ist mein fehler?

    genau das passiert doch mit shades op= 😕
    schau ihn dir nochmal genau an.

    also ich weis jetzt nicht wie die Funktion swap aussehen soll aber ich stelle mir da sowas vor...

    swap( klasse i)
    {
        klasse puffer;
        puffer.a = this->a;
        this->a = i.a;
        i.a = puffer.a;
        //usw....
    }
    

    das tauschen wie es im "Duden" definiert ist A bekommt alles von B und B bekommt alles von A
    ich versteh nicht wie das mit dem Zuweisungsoperator zutun hat 😕



  • Online schrieb:

    also ich weis jetzt nicht wie die Funktion swap aussehen soll aber ich stelle mir da sowas vor...

    habe ich doch gesagt: es tauscht die zeiger:

    void swap(klasse& other)
    {
      std::swap(a, other.a);
      std::swap(b, other.b);
    }
    

    Funktioniert sehr gut wenn a und b nur primitive typen sind, oder selber std::swap spezialisiert haben

    das tauschen wie es im "Duden" definiert ist A bekommt alles von B und B bekommt alles von A
    ich versteh nicht wie das mit dem Zuweisungsoperator zutun hat 😕

    Zuweisung ist: copy and swap
    du erstellst per CopyCtor eine Kopie und tauscht diese kopie dann mit dem objekt dem du den wert zuweisen willst aus.

    der vorteil liegt auf der hand: extrem wenig code, exceptionsicher und nicht wirklich langsamer als ein herkoemmlicher op= (merzt aber _alle_ fehlerquellen aus)



  • desweiteren mach ich persönlich nen bogen um assert solange es kein fehler ist,der verhindert, dass mein programm noch weiterlaufen kann,

    Das ist ein fehler deinerseits.
    assert ist fuer _alle_ logikfehler.

    ja super, was bringt mir das? im zweifesfall starte ich das programm, und das kickt mich sofort beim kleinsten logischen fehler.
    assert ist mir zu extrem, ich will auf ne selbstzweisung aufmerksam gemacht werden,zb halt durch ne exception, die abgefangen wird und dann in irgendeine debug box schreibt: achtung selbstzuweisung in datei xxx.txt zeile 16 funktion xxx(parameter1,parameter2,parameter3)



  • otze schrieb:

    ja super, was bringt mir das? im zweifesfall starte ich das programm, und das kickt mich sofort beim kleinsten logischen fehler.
    assert ist mir zu extrem, ich will auf ne selbstzweisung aufmerksam gemacht werden,zb halt durch ne exception, die abgefangen wird und dann in irgendeine debug box schreibt: achtung selbstzuweisung in datei xxx.txt zeile 16 funktion xxx(parameter1,parameter2,parameter3)

    interessant - das programm kann dann normal weiterlaufen? eine zuweisung ist fehlgeschlagen, bei mir ist das meistens dann ein kritischer fehler...

    naja, aber inwiefern ist es besser eine exception zu werfen und dann eine textbox auszugeben (btw: wo faengst du die denn dann ab??) als ein feines assert, dass die selbse ausgabe macht?

    wobei selsbtzuweisung _keine_ Fehler ist.
    ich habe genug argumente genannt.



  • wer sagt, dass ich die zuweisung fehl schlagen lasse? ich setz halt nur zusätzlich ne exception,der programmierer wird davon in kenntnis gesetzt und das programm läuft danach normal weiter.

    wobei selsbtzuweisung _keine_ Fehler ist.
    ich habe genug argumente genannt.

    meinst du das argument, wo du sagst, dass du die selbstzuweisung in kauf nimmst oder das,wo du sagst, dass es einfacher ist,im operator eine selbstzuweisung abzufangen?🙄

    naja, aber inwiefern ist es besser eine exception zu werfen und dann eine textbox auszugeben (btw: wo faengst du die denn dann ab??) als ein feines assert, dass die selbse ausgabe macht?

    da ich normalerweise nur grafikanwendungen schreibe, ist mir klar, wo ich diese exception abfange: in einem catchblock in der main, welcher sich der DebugExceptionHandling Class bedient(rendert ganz am schluss eines programmschleifendurchgangs eine infobox mit etwaigen fehlermeldungen oben rechts in die ecke)



  • otze schrieb:

    wer sagt, dass ich die zuweisung fehl schlagen lasse? ich setz halt nur zusätzlich ne exception,der programmierer wird davon in kenntnis gesetzt und das programm läuft danach normal weiter.

    Wie geht das?

    a=b;
    cout<<a;

    wie wird cout<<a ausgefuehrt, wenn a=b eine exception wirft?

    meinst du das argument, wo du sagst, dass du die selbstzuweisung in kauf nimmst oder das,wo du sagst, dass es einfacher ist,im operator eine selbstzuweisung abzufangen?🙄

    und was ist mit "do as the ints do"?

    wie waere es mit:

    bool fill(T& obj, foo kriterium)
    {
      iterator i=find(kriterium); //aus irgendeiner liste oder aehnliches laden
      if(i!=end)
      {
        obj=*i;
        return true;
      }
      return false;
    }
    

    wenn ich da das naechste mal wieder das selbe objekt bekomme, dann ist das doch kein fehler, oder? es ist mir ja egal was vorher in obj drinnen stand...

    da ich normalerweise nur grafikanwendungen schreibe, ist mir klar, wo ich diese exception abfange: in einem catchblock in der main, welcher sich der DebugExceptionHandling Class bedient(rendert ganz am schluss eines programmschleifendurchgangs eine infobox mit etwaigen fehlermeldungen oben rechts in die ecke)

    und dann laeuft die anwendung ganz normal weiter? der ganze durchlauf wird doch durch die exception abgebrochen...



  • Hallo.

    Habe den Quelltext nochmal verändert. Das Programm läuft soweit zufriedenstellend, will heissen es ist der Aufgabenstellung gerecht geworden. Auch wenn ich nicht alle Antworten recht zuordnen kann, habe ich versucht das Beste draus zu machen.

    Würde mich über weitere Kommentare freuen.

    Gruß,
    Johrtreel



  • Shade Of Mine schrieb:

    Warum sollte ich zB folgendes kommentieren:

    template<class Policy>
    class Timer
    {
    public:
      typedef typename Policy::value_type value_type;
    private:
      value_type value;
    
    public:
      Timer()
      {
        Policy::now(value);
      }
    
      value_type elapsed() const
      {
        return Policy::elapsed(value);
      }
    };
    

    sollst du, von mir aus zumindest, auch gar nicht, denn da hast du ein Paradebeispiel angegeben, bei dem auch ich mich schwer tun würde, etwas zu kommentieren (nein, nicht weil ich den Code nicht verstehe) 🙂

    Ich hatte aber schon Chefs/Projektleiter, die auch dort gerne Kommentare gesehen hätten (na gut, ich gebe zu, etwas komplexer war die Klasse schon, aber für mich nur unwesentlich), es gibt nichts, was es nicht gibt.

    Shade Of Mine schrieb:

    wo passen da kommentare hin?

    in die Leerzeilen z.B., SCNR 🙂 🙂

    Oder um Tools einzusetzen, die aus Kommentaren Dokus erzeugen können ?

    Shade Of Mine schrieb:

    das ist n gutes argument - brauch ich aber nur wenn ich eine library schreibe.

    du Glücklicher, manchmal kann man es sich nicht aussuchen, siehe oben 😉

    Shade Of Mine schrieb:

    sobald ich einen kommentar brauche um den code zu verstehen, schreibe ich ihn um.

    in der arbeit geht das zwar nicht - aber dort gibt es auch keine doku oder aehnliches :p

    in den wenigsten Fällen schreibe ich Kommentare für mich (wenn man mal von so Dingen wie /* TO DO */ oder ähnlichem absieht), es geht ja nicht um "Selbstbeweihräucherung" des Codes oder um eine besonders schöne Verpackung des Codes in Kommentaren. Auch verliert die Kommentierung an Bedeutung für einen selbst, wenn man schon eine gewisse "Klasse" in der Programmierung erreicht hat, es geht, wie ich schon angedeutet habe, oft um externe oder interne Vorgaben (oder um "Weitsicht", wenn man weiß, daß der Code bald von anderen zur Pflege/Weiterentwicklung übernommen wird, und man nicht unbedingt von den Fragen des "Nachfolge-Entwicklers" genervt werden will 🙂 )

    MfG


Anmelden zum Antworten