Forum: PC-Programmierung Cpp17 - Copy assignment (array member)


von Daniel Larousso (Gast)


Lesenswert?

Áloa,

habe ein Beispiel eines Copy Assignments, dass mir noch nicht ganz 
einleuchtet.
Als Beispiel dient eine einfache Klasse, die einen int-pointer hat, 
welcher später auf ein Array zeigt und ein int, welches numerisch die 
Anzahl der Elemente hält. Das ist natürlich kein OOP-stil, aber ich 
denke, es geht hier einfach um das handlen verschiedener Typen.

>private member
1
class MyMovableClass
2
{
3
private:
4
    int _size;
5
    int *_data;

>der Copy Assigment dazu
1
 MyMovableClass& operator=(const MyMovableClass &source)
2
 {
3
   if (this == &source) return *this;
4
   delete[] _data;
5
   _data = new int[source._size];
6
   *_data = *source._data;
7
   _size = source._size;
8
   return *this;
9
}

>>Zwei Dinge sind mir nicht klar:
>1) das delete[] _data; Warum ist das da?
>2) Die Zeile mit " *_data = *source._data; "
_data lese ich als Zeiger auf ein Arry, was bedeutet, _data zeigt auf 
das nullte Element, was wiederum bedeutet *_data referenziert den Wert 
im nullten Element. Dem entsprechend macht *source._data das gleiche.
'Ich' lese die Zeile also als "Kopiere den Wert, der im nullten Element 
von source steht in das nullte nullte Element des Zieles"
Die Erklärung von dem Beispiel spricht von Deep-Copy, doch für mich 
sieht es aus, als würde tatsächlich nur der numerische Wert des nullten 
Elementes kopiert werden und er Rest geht verloren.

von Nils (Gast)


Lesenswert?

Du hast völlig recht, das der copy Operator, so wie er hier 
implementiert ist, nur das erste Element kopiert.

(und das übrigens auch, wenn size == 0 ist. Das ist ein böser Bug, weil 
es ein undefinierter Speicherzugriff ist).

Korrekt macht man das mit memcpy.
1
   memcpy (_data. *source._data, source._size * sizeof (int));

Ist ein schlechter Beispiel Code für Deep-Copy, den Du hier zeigst.

Deep-Copy bedeutet, das beim kopieren des Objektes eine komplett 
eigenständige Kopie der Daten angelegt werden, anstatt einfach den 
Pointer zu kopieren.

von Flaumträger (Gast)


Lesenswert?

Daniel Larousso schrieb:
> habe ein Beispiel eines Copy Assignments, dass mir noch nicht ganz
> einleuchtet.

Woher stammt das denn?

>>1) das delete[] _data; Warum ist das da?

Das wäre sowohl für eine Kopie als auch für Move falsch.

Kopie: Array kopieren (wie Nils schon schrieb: Deep-Copy) per std::copy 
oder std::memcpy, _size im Zielobjekt setzen.

Move: _size und den _data-Zeiger kopieren, im Quellobjekt _size = 0 und 
_data = nullptr setzen.

(im Kern und ohne Tests auf Selbstkopie, Speicher etc.)

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Noch ein potentielles Problem: Falls der Destruktor so aussieht:
1
MyMovableClass::~MyMovableClass () {
2
  if (_data)
3
    delete [] _data;
4
}

Wenn dann hier:

Daniel Larousso schrieb:
> delete[] _data;
>    _data = new int[source._size];

das "new" eine Exception wirft, zeigt "_data" immer noch auf das gerade 
gelöschte Array, dadurch wird im Destruktor nochmal versucht es 
freizugeben, was einen Absturz bewirkt. Also besser so:
1
MyMovableClass& operator=(const MyMovableClass &source)
2
 {
3
   if (this == &source) return *this;
4
   delete[] _data; _data = nullptr;
5
   _data = new int[source._size];
6
   _size = source._size;
7
   std::copy (source._data, source._data + _size, _data);
8
   return *this;
9
}

Jetzt haben wir eine "weak exception guarantee" - wenn im "new" oder 
"copy" Exceptions auftreten, ist die Instanz in einem "sicheren" 
Zustand, d.h. der Destruktor kann aufgerufen werden. Außerdem ist 
"_size" auf jeden Fall korrekt gesetzt, auch wenn "copy" eine Exception 
wirft. Die "strong exception guarantee" ist aber nicht gegeben; bei 
einer Exception sind die vorherigen Daten im Objekt gelöscht und der 
vorherige Zustand wird nicht beibehalten. Das zu implementieren ist 
interessanter...

von Wilhelm M. (wimalopaan)


Lesenswert?

Daniel Larousso schrieb:
>>>Zwei Dinge sind mir nicht klar:
>>1) das delete[] _data; Warum ist das da?

Falls das Zielobjekt ein rohes Arrays (_data) besitzt, so muss das erst 
zerstört werden, bevor _data überschrieben wird, sonst hast Du ein 
Speicherleck.

Ist _data nullptr, so ist das delete[] ein noop. Da die Klasse keine 
in-class-initializer verwendet, hoffe ich, dass die ctoren die 
Datenelemente richtig initialisieren.

>>2) Die Zeile mit " *_data = *source._data; "

Kopiert ein int-Objekt. Das kann richtig sein, vermute aber, es ist - 
wie oben schon erwähnt - falsch.

Wenn der op= geschrieben wurde, ist es sehr wahrscheinlich, dass auch 
der cctor geschrieben wurde (hast Du nicht veröffentlich). Daher schau 
Dir mal das Copy-Swap-Idiom an.

von 123 (Gast)


Lesenswert?

Daniel Larousso schrieb:
> int _size;
> int *_data;

Noch eine allgemeinere Bemerkung: gewöhne dir nicht an zB. gewöhnliche 
Variablen mit einem oder gar zwei führenden Unterstrichen zu versehen. 
Das kann mit etwas Pech zu Konflikten führen.

von Torsten R. (Firma: Torrox.de) (torstenrobitzki)


Lesenswert?

Niklas G. schrieb:
> Die "strong exception guarantee" ist aber nicht gegeben; bei
> einer Exception sind die vorherigen Daten im Objekt gelöscht und der
> vorherige Zustand wird nicht beibehalten. Das zu implementieren ist
> interessanter...

Und kann trivial sein:
1
MyMovableClass& operator=(const MyMovableClass& source)
2
{
3
   MyMovableClass copy(source);
4
   std::swap( _data, copy._data );
5
   std::swap( _size, copy.__size );
6
7
   return *this;
8
}

wenn std::swap() für MyMovableClass implementiert ist und man kein 
Problem damit hat, die Funktionssignatur zu ändern, wird es noch 
übersichtlicher:
1
MyMovableClass& operator=(MyMovableClass copy)
2
{
3
   std::swap(copy);
4
5
   return *this;
6
}

Wenn man beim Beispiel des OPs bleiben will, müssen einfach alle 
Aktionen, die fehlschlagen können durchgeführt werden, bevor das Objekt 
geändert wird:
1
MyMovableClass& operator=(const MyMovableClass &source)
2
{
3
   if (this == &source) return *this;
4
5
   const auto new_data = new int[source._size];
6
7
   // commit
8
   delete[] _data; 
9
   _data = new_data;
10
   _size = source._size;
11
12
   std::copy (source._data, source._data + _size, _data);
13
14
   return *this;
15
}

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.