Á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
>>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.
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.
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.
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.)
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:
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...
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.
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.
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:
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=(MyMovableClasscopy)
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: