Modernes C++ programmieren

Okt 07, 2013

Swap-Operator?

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.

posted at 10:20  ·   ·  assign  C++  C++1y  copy  operator  swap

Apr 03, 2012

Mit C++11 hört C++ nicht auf -- unbounded-precision integer types

Mit der Verabschiedung von C++11 gibt sich die Gemeinde natürlich nicht zufrieden. Auch C++ wird sich natürlich noch weiter entwickeln. Einige Dinge wurden aus C++0x herausgelassen, damit “0x” es letztendlich wenigstens “11” werden konnte. Man wird aber sicher alte Bekannte irgendwann wiedersehen.

Dazu gehören zuvorderst natürlich die Concepts, aber viele erwarten auch Asynchrone IO oder Filesystem- und Dateinamenbehandlung – oder irgendetwas aus dem Umfeld von Boost.

C++1y, TR, oder Papierkorb

Was in den Standard Einzug halten wird, ob wann, oder ob es einen Nachtrag oder Technical Report mit Erweiterungen geben wird, steht natürlich in den Sternen. Nein, nicht in den Sternen – man kann sich an den Diskussionen ja beteiligen und vielleicht deren Ausgang ein wenig beeinflussen. Für den Interessierten lohnt es sich also, die Augen offen zu halten…

Es tut sich viel, aber ich greife mal exemplarisch etwas heraus…

integer und unsigned_integer

Neu in der Diskussion ist eine kleine aber feine Erweiterung für Ganzzahlen mit unbegrenzter Genauigkeit. “Unbegrenzt” meint hier dynamisch unbegrenzt, im Vergleich zu statisch unbegrenzt. Es wäre ein wenig leichter, zum Beispiel einen Zahltypen zu definieren, bei dem man bei der Initialisierung eine maximale Länge angeben muss – überschreitet man die, gäbe es einen Überlauf. Statt aber die Klasse integer auf einer Art festem Array zu basieren, schwebt den Autoren von N3375 konzeptionell eher ein vector vor. So kann kein Überlauf mehr passieren, die Größe passt sich automatisch an – bis der Speicher ausgeht…

Da gibt es natürlich schon jede Menge Bibliotheken (zum Beispiel die vielseitige Gnu GMP) , aber die wenigsten schmecken nach C++11.

Mit den bestehenden Zahlentypen wird integer natürlich interagieren. Die arithmetischen wie +, * und Verwandte werden mit sich selbst und int-Varianten unterstützt.

    integer i = 30000;
    integer j = 1000 * i;

Allerdings geschieht dies im aktuellen Vorschlag nicht durch Überladung von operator+() etc mit allem möglichen int -Varianten, sondern durch die implizite Umwandlung von int in integer.

Der aktuelle Vorschlag bietet:

  • arithmetische Operationen +, -, *, /, %, – und ++
  • eine Funktion div(), die gleichzeitig das ganzzahlige Ergebnis und den Rest zurückgibt,
  • Bitoperationen |, &, \^, \<\<, >>, \<\<=, >>=, |=, &= und\^=.
  • Vergleiche ==, \<, >, !=, \<= und >=
  • Quadrat, Quadratwurzel sowie `sqrtrem()` um gleichzeitig Quadratwurzel und Rest zu erhalten
  • Exponenzieren mit pow(), bzw. powmod() – letzterest ist ein häufiger Anwendungsfall, der eine platzsparendes pow() gefolt von einer Modulo-Operation ist. Den GGT und KGV kann man mit gcd() und lcm() berechnen.
  • Eine beliebig lange Zufallszahl kann man erstellen,
  • Stream-Ein- und Ausgabe wird unterstützt; mit `to_string(base=10)` ist ebenfalls praktisch.

Noch Offen

Chancen sich einzubringen hat man vielleicht insbesondere bei den noch offenen Punkten des aktuellen Vorschlags:

  • Wie soll die Speicherverwaltung duch den Benutzer beeinflußt werden können? Wie sollen Allocators Eingang finden?
  • Was soll passieren bei einem Unterlauf, zum Beispiel bei
    • unsigned_integer neg = -99;?
    • unsigned_integer five = 5 und dann unsigned_integer result = five - 100?
    • unsigned_integer five = 5; unsigned_integer ten = 10, dann five - ten?
  • Wie ist mit Präzisionsverlust umzugehen, wenn ein sehr großer integer in einen int umgewandelt werden soll?

Wer hat Vorschläge?

Links

N3375 - Proposal for Unbounded-Precision Integer Types