Published: Mon 07 October 2013
By towi
In C++ .
tags: assign C++ C++1y copy operator swap
Manuelles Swap
Elemente zu vertauschen ist an vielen Stellen eine gefährliche Operation. Zu Zeiten ohne Exceptions und ohne *
Multithreading* war vertauschen noch einfach:
Typ temp = a;
a = b;
b = temp;
Aber was passiert hier genau:
Typ temp = a;
– erzeugt eine neue Instanz und initialisiert sie mit a
, per Copy Constructor Typ(const Typ&)
.
a = b;
– ruft den Zuweisungsoperator Typ& operator=(const Typ&)
auf
b = temp;
– ruft noch einmal den Zuweisungsoperator Typ& operator=(const Typ&)
auf.
Weil es eine neue Instanz ist, wird temp
später per Destruktor weggeräumt.
Problempunkte
Bei diesem Prozedere gibt es vor allem zwei Dinge zu beachten:
Die Ressourcen, die Typ
hält, werden diese wegen des Copy C’tors einmal kopiert .
Schade, wenn dies zum Beispiel ein riesengroßer Speicherblock ist, zu dem vielleicht nur ein Pointer getauscht werden müsste
Manche Resourcen können nicht kopiert werden: Locks, Mutexe, Filehandles machen Probleme, etc. Dann funktioniert das ganze Vorgehen nicht.
Was, wenn b = temp
eine Exception wirft? Der Zuweisungsoperator sollte das nicht, aber wenn man dessen Code nicht unter Kontrolle hat? Dann sind a
und b
nicht vertauscht, sondern identisch. Das alte a
ist verloren.
Der bessere Weg
Daher wird seit jeher die Verwendung der swap
-Funktion aus der Standardbibliothek empfohlen:
using std :: swap ;
swap ( a , b );
oder seit C++11 auch einfach
denn die Namensauflösung der swap
-Implementierung wurde leicht geändert (Details im Buch ;-) oder
bei Stackoverflow ).
Edit: Es ist ein Idiom, die swap
-Funktion eine friend
-Memberfunktion zu machen, auch dazu mehr
bei Stackoverflow .
Auch swap
soll – wie der Zuweisungsoperator (und andere Beteiligte) – keine Exceptions werfen. Und da für die swap
-Implementierung empfohlen wird, die Member elementweise wieder mit swap
zu vertauschen, würde statt der Kopie der
Ressourcen * im Falle des großen Datenblocks nur der Pointer ge-swapped * im Falle der nicht-kopierbaren Ressource
diese vertauscht, was gehen sollte.
Daher merke (seit jeher): Eigene Datentypen sollten swap
implementieren .
Neuer Operator :=:
Um die Wichtigkeit von swap
zu unterstreichen, gibt es
den Vorschlag ,
swap
als Operator :=:
einzuführen. Das sähe dann so aus:
struct Typ {
Data a ;
Feld b ;
} ;
Typ & operator :=:( Typ & lhs , Typ & rhs ) noexcept {
lhs. a : =: std :: move ( rhs . a );
lhs. b : =: std :: move ( rhs . b );
return lhs ;
}
Typ & operator :=:( Typ & lhs , Typ && rhs ) noexcept {
return rhs ;
}
Statt nacheinander die Elemente mit swap
zu vertauschen verwendet man den neuen :=:
-Operator. Beziehungsweise
die &&
-Variante, die für das swappen mit der rhs
-Instanz da ist, die ohnehin bald weggeworfen würde gestaltet sich
die Implementierung besonders leicht: Nimm als lhs
einfach rhs
.
Dadurch ist hervorgehoben, dass es sich um eine sehr spezielle Funktion handelt, und kommt somit namenstechnisch
dem operator=
näher. Der Benutzer wird eher darauf gestoßen, dass er sie implementieren sollte.
Es gibt nich weitere
kleine Ergänzungen
für :=:
, zum Beispiel für die eingebauten Typen.
Glaskugel
Hauptsächlich handelt es sich um eine Sytnax-Erweiterung mit nur wenig funktionalen Ergänzungen. Meine Vorraussage ist,
dass dieses Feature es nicht in den Standard schafft. Wenn es nur um Syntax geht, war das Kommitee eher zurückhaltend.