Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Problem beim Überladen von operator=

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
05.04.2003, 13:28 Uhr
Chillmaster



Hi

Aaaaaalso:
Ich versuche gerade etwas tiefer in die C++ - Programmierung einzusteigen. In dem Buch, das ich dazu verwende wurde anhand einer selbstgeschriebenen String-Klasse demonstriert, wie man mit Arrays, Zeigern auf Arrays, Überladen von Operatoren usw. arbeitet.
Hab ne Weile gebraucht, bis ich alles kapiert hab. Ich versteh nun nur eines nicht:
In der Klasse wird der = - Operator überladen und zwar folgendermaßen:


C++:
String & String::operator= (const String & rhs)
{
  if (this == &rhs)
    return *this;
  delete[] itsString;
  itsLen = rhs.GetLen();
  itsString = new char[itsLen + 1];
  for (unsigned short i = 0;  i < itsLen;  i++)
    itsString[ i ] = rhs[ i ];
  itsString[itsLen] = '\0';
  return *this;
}




Wie zu sehen ist, heißt die Klasse String.
Die ersten beiden Zeilen der Klasse testen, ob auf der rechten Seite vom operator das gleiche String - Objekt steht wie auf der linken und gibt den linken String wieder zurück -> Keine Veränderung.
Wenn ich den Test ausklammere (und das return this natürlich auch), und schreibe:
String1 = String1
stehen hinterher lauter '=' in dem String.

Das hatte mich verblüfft, aber ich dachte, nagut, dann setzt der Compiler eben einen Standardwert nach dem Löschen mit delete[]. Und der ist eben das '=' für jede Stelle des Strings.

ABER:
Wenn ich INNERHALB der Elementfunktion, welche ich oben beschrieben habe den String ausgebe stehen dort lauter | in dem String.
Außerhalb (also in main, von wo aus auch der Aufruf String1 = String1 erfolgte), stehen aber trotzdem '=' im String.

Woran liegt das?


Naja, falls das Ganze keiner verstanden hat, weil ich es nicht erklären kann, hier nochmal der komplette Programmcode:


C++:
// Listing 12.12 - Verwendung einer String - Klasse
#include <iostream.h>
#include <conio.h>
#include <string.h>

// Rudimentäre String-Klasse
class String
{
public:
// Konstruktoren
  String();
  String (const char * const cString);
  String (const String & rhs);
  ~String();

// Ueberladene Operatoren
  char operator[] (unsigned short offset) const;
  String & operator= (const String &);

// Allgemeine Zugriffsfunktionen
  unsigned short GetLen() const  { return itsLen; }
  const char * GetString() const  { return itsString; }

private:
  String (unsigned short); // privater Konstruktor
  char * itsString;
  unsigned short itsLen;
};

// Standardkonstruktor erzeugt einen String von 0 Byte
String::String()
{
  itsString = new char[1];
  itsString[0] = '\0';
  itsLen = 0;
}

// privater (Hilfs-)Konstruktor, wird nur von Klassenmethoden verwendet,
// um einen neuen Null-String von erforderlicher Größe zu erzeugen.
String::String(unsigned short len)
{
  itsString = new char[len+1];
  for (unsigned short i = 0;  i <= len;  i++)
    itsString[ i ] = '\0';
  itsLen = len;
}

// Konvertiert ein Zeichenarray in einen String
String::String(const char * const cString)
{
  itsLen = strlen(cString);
  itsString = new char[itsLen+1];
  for (unsigned short i = 0;  i < itsLen;  i++)
    itsString[ i ] = cString[ i ];
  itsString[itsLen] = '\0';
}

// Kopierkonstruktor
String::String (const String & rhs)
{
  itsLen = rhs.GetLen();
  itsString = new char[itsLen + 1];
  for (unsigned short i = 0;  i < itsLen;  i++)
    itsString[ i ] = rhs[ i ];
  itsString[itsLen] = '\0';
}

// Destruktor, gibt zugewiesenen Speicher frei
String::~String ()
{
  delete[] itsString;
  itsLen = 0;
}

// Selbstzuweisung prüfen, Speicher freigeben, dann String und Größe kopieren
String & String::operator= (const String & rhs)
{
//  if (this == &rhs)
//    return *this;
  delete[] itsString;
  cout << "\n\tTests:\n";
  cout << "\t\trhs.GetString(): " << rhs.GetString() << endl;
  cout << "\t\tGetString(): " << GetString() << endl;
  cout << "\tEnde der Tests...\n\n";
  itsLen = rhs.GetLen();
  itsString = new char[itsLen + 1];
  for (unsigned short i = 0;  i < itsLen;  i++)
    itsString[ i ] = rhs[ i ];
  itsString[itsLen] = '\0';
  return *this;
}

// konstanter Offset-Operator für konstante Objekte (siehe Kopierkonstruktor)
char String::operator[] (unsigned short offset) const
{
  if (offset > itsLen)
    return itsString [itsLen-1];
  else
    return itsString [offset];
}

int main()
{
  String s1("Erster Test");
  cout << "s1:\t\t" << s1.GetString() << endl;

  char * temp = "Hello World";
  cout << "\nJetzt erfolgt die Zuweisung der beiden gleichen Strings  (s1 = s1)." << endl;
  s1 = s1;
  cout << "s1:\t\t" << s1.GetString() << endl;

  getch();
  return 0;
}

--
With Great Power Comes Great Responsibility

Dieser Post wurde am 09.04.2003 um 16:33 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
05.04.2003, 14:40 Uhr
~abcSchütze
Gast


Ich glaub ich habe auch keine Ahnung davon. Referenzen versuch ich immer zu vermeiden.

Du kannst ja mal versuchen deinen vergleich so aufzurufen

if (this == rhs) return *this;

also das & weglassen. Ich glaub das ist schon automatisch dereferenziert.

Du übergibst in der Funktion quasi ein direkte hardcopy des Pointers.
wenn du String::operator= (const String* rhs) so aufrufen würdest(was ja der compiler nicht erlaubt) wird auf dem stack erst eine copy des pointer angelegt die danach( nach ende der Funktion) nicht mehr existiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
05.04.2003, 15:16 Uhr
Chillmaster



oh shite, jetzt hab ich aber mist gebaut. Kann den oberen Post nicht löschen, da ich als Gast drin war.

Hier nochmal in richtiger Form:

Glaub du hast mich ein bissel falsch verstanden.
Der Test den du ansprichst und den ich im Programm "auskommentiert" hab, der funktioniert einwandfrei.
Es geht mir darum, wenn ich den Test weglasse und genau der Fall eintritt (Also dass this = &rhs ist), gibt das Program sehr merkwürdige Sachen von sich. ich poste mal die Ausgabe:


Code:
s1:             Erster Test

Jetzt erfolgt die Zuweisung der beiden gleichen Strings  (s1 = s1).

        Tests:
                rhs.GetString(): ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
                GetString(): ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
        Ende der Tests...

s1:             ===========



Wollte da eigentlich wissen, warum innerhalb der Elementfunktion (das eingerückte) diese komischen Zeichen stehen: '¦' und außerhalb an GENAU DER GLEICHEN ADRESSE das '=' - Zeichen steht?????

EDIT:
Ich hab die Zeichen jetzt mal genauer untersucht: Das Zeichen innerhalb der Elementfunktion hat den ASCII - Code 221.
Außerhalb wird der ASCII - Code 205 angezeigt.
--
With Great Power Comes Great Responsibility

Dieser Post wurde am 05.04.2003 um 15:25 Uhr von Chillmaster editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
05.04.2003, 16:01 Uhr
~Heiko
Gast


Sorry, dann ist mir das auch nicht so klar, warum das nicht funzt. Sollte eigentlich.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
004
05.04.2003, 16:31 Uhr
TBO



Hallo,

Mir fallen spontan zwei Dinge auf bei Selbstzuweisung x = x:

Im Falle this == &rhs sind die beiden Objekte nicht nur semantisch gleich, d.h. sie enthalten diesselbe Zeichenkette, sondern sind _identisch_, d.h. wenn du itsString des "linken Objekts" löscht:

delete[] itsString;

...löscht Du auch itsString in rhs!
Deswegen funktioniert hinterher weder die Ausgabe
von rhs.itsString noch das Kopieren von rhs.itsString
an das neu angelegte Array. Was beim Zugriff auf einen gelöschten Array passiert ist undefiniert, ob nun "=" oder "|" drinnensteht ist wohl eher Zufall.

Das kommt mir auch seltsam vor (sowas steht auch im Copy-Constructor):

for (unsigned short i = 0; i < itsLen; i++)
itsString = rhs;

Sollte es nicht eher heißen:

for (unsigned short i = 0; i < itsLen; i++)
itsString[i] = rhs[i];


Und noch ein Tipp, falls das nicht schon klar ist:

String s1("blub");
String s2("blub");

Der Vergleich (s1 == s2) würde immer false liefern, da die beiden Objekte
zwar diesselbe Zeichenkette speichern, die Variable itsString aber nur die Speicheradresse zum "blub\0"-String enthält, und die ist in s1 eine andere als in s2. Für korrektes Verhalten müsstest Du die ==-
und !=-Operatoren auch noch überladen.

Frank
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
005
05.04.2003, 16:37 Uhr
TBO



Argh, das Forum hat die [-Klammern gefressen. Ich versuche es nochmal:
Man möge sich die "-" wegdenken.


Zitat:

Sollte es nicht eher heißen:

Code:
for (unsigned short i = 0; i < itsLen; i++)
    itsString[-i-] = rhs[-i-];





Ich nehme an, die Klammern waren auch in deinem Code ursprünglich da und wurden vom Forum vernichtet. (Da der i-Tag für kursiv steht).

Dieser Post wurde am 05.04.2003 um 16:41 Uhr von TBO editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
006
09.04.2003, 16:06 Uhr
Chillmaster



Ja, die Klammern waren auch bei mir schon da

Also mir ist schon klar, dass ich mit &rhs die Referenz übernommen habe. Da auf der linken Seite (*this) das gleiche steht wie auf der Rechten Seite (rhs) hat das löschen fatale Folgen. Das war mir schon klar, deshalb hab ichs ja getestet.
Dass zwei verschiedene String - Objekte mit gleichem Inhalt nicht als gleich interpretiert werden war beabsichtigt. Es ist ja kein Problem s1 zu löschen und an s1 dann s2 zu übergeben.

Aber, ich glaub ich kann ein Problem wirklich nicht auf den Punkt bringen.
Also versuch ichs nun mal in einem Satz zu formulieren:
Wie kann es sein, dass an der Adresse auf die this zeigt ZWEI VERSCHIEDENE SACHEN STEHEN.

Innerhalb der Elementfunktion lass ich mir den Inhalt von rhs und this (Variable itsString) ausgeben beide haben natürlich den selben Inhalt.
Dann wird rhs an this zugewiesen mittels der oben genannten for - Schleife.
Ah, ich seh gerade, es wird für this ja nochmal neuer Speicher allokiert, kurz vor der for - Schleife:

Code:
itsString = new char(itsLen+1);


Dadurch ändert sich auch rhs oder?
Kann es evtl. sein, dass an der alten Adressen, welche im Heap gelöscht wurde eben diese | standen, und an der von Microsoft Visual C++ neu zugewiesenen Adresse das = stand?

Hab das Programm mal mit Borland C++ Builder kompiliert.
Da steht in itsString innerhalb der Elementfunktion (vor der Zuweisung) das gleiche wie nach der Zuweisung in main().
Es liegt wohl einfach an der Speicherverwaltung des Compilers.
--
With Great Power Comes Great Responsibility
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
007
10.04.2003, 08:14 Uhr
virtual
Sexiest Bit alive
(Operator)


Wenn Ich dazu auch noch meinen Senf loswerden darf: Im Zuweisungsoperator wird ja kräftig Speicher freigegeben und belegt. Von daher ist er nicht exceptionsicher: Nehmen wir an, du hast folgende Situation:

C++:
String a = "Chillmaster";
String b;

b = a;


Wenn man nun die erste Implementation des op= nimmt, die du geschrieben hast (an der bis auf den folgenden Punkt nichts auszusetzen wäre), dann kann es ja passieren, daß bei dem "itsString = new char[itsLen + 1];" eine Exception bekommst, weil kein Speicher da ist. Das hat zwei Auswirkungen:
1. Die linksseitige Instanz wird durch den Aufruf verändert, obwohl eine Exception geworfen wurde. Das ist nicht unbedingt zu erwarten
2. Der Zusatnd von der linksseitigen Instanz ist inkonsistent, weil istString!=NULL, aber auf bereits gelöschgten Speicher zeigt.
Üblicherweise erzeugt man kann das so umgehen:

C++:
class String
{
private:
   char * itsString;
   unsigned short itsLen;
   ...
   // Vertauscht die Inhalte mit dem anderen Objekt:
   void swap(String& rOther)
   {
       std::swap(itsString, rOther.itsString);
       std::swap(itsLen, rOther.itsLen);
   };
   ...
public:
   String& operator = (const String& rOther)
   {
      // Kann aus performance Gründen bleiben, ist aber nicht zwingend erforderlich:
      if (this== &rOther) return *this;
      String temp(rOther); // Hier kann eine Exception kommen, aber wir haben nichts verändert an this!
      swap(temp); // Swap throwt nicht, wir haben jetzt den Inhalt von rOther, temp unseren alten
      return *this; // Danach wird temp destruiert, also auch unser alter Wert
   };
   ...
};


--
Gruß, virtual
Quote of the Month
Ich eß' nur was ein Gesicht hat (Creme 21)

Dieser Post wurde am 10.04.2003 um 08:15 Uhr von virtual editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: