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 ConstructorTyp(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

std::swap(a, b);

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.