C++11 – auch ein Stimmungsbild

Seite 3: Top 11 der C++11-Features II

Inhaltsverzeichnis

Range-based for-loops, so die englische Bezeichnung, vereinfachen das vollständige Iterieren über Container, ähnlich wie foreach in vielen anderen Sprachen:

const std::vector<int> vi = ...;
for ( int i : vi ) // read: for each i in vi
...;

Auch hier darf der Entwickler faul sein und auto schreiben:

const auto vi = ...;
for ( auto i : vi )
...;

Die Syntax funktioniert mit allen Containern, die begin()- und end()-Methoden enthalten.

Eine statische Zusicherung (static_assert) funktioniert wie das klassische assert()-Makro, jedoch zur Übersetzungszeit und nicht zur Laufzeit. Damit wäre eigentlich alles gesagt, die Nützlichkeit stellt schließlich BOOST_STATIC_ASSERT seit Jahren in der Praxis unter Beweis.

Provokant weit unten in den Top 11 steht die Multithreading-Unterstützung in C++11. Das liegt darin begründet, dass Entwickler mit C++ seit Dekaden ohne Unterstützung des Standards Programme schreiben, die Multithreading verwenden. Damit seien das moderne Speichermodell und die neuen Thread-Klassen nicht abgewertet. Wer jedoch Multithreading in C++ betreibt, dem helfen Pthreads, Boost. Thread, oder OpenMP schon seit Jahren bei der Arbeit, und mit Ausnahme von Boost.Thread unterscheiden sich die Ansätze derart stark von std::thread & Co.,dass nur wenige Projekte es sich leisten können, in absehbarer Zeit auf die Standard-Werkzeuge zu portieren.

Copy-Konstruktoren sind integraler C++-Bestandteil. Es ist aber interessant zu beobachten, dass der Entwickler in vielen Fällen, in denen die Sprache den Aufruf des Copy-Konstruktors vorschreibt, tatsächlich keine Kopie herstellen, sondern lediglich den Wert verschieben wollte. Sei es nun das Zurückliefern einer lokalen Variablen als Teil eines return-Ausdrucks, das Einfügen eines Werts in einen std::vector, die merkwürdige Eigentümerwechsel-Semantik des std::auto_ptr oder die Implementierung des primären std::swap()-Funktions-Templates. In diesen Situationen ist es unnötig, die Quellvariable intakt zu halten. Für einige der Fälle hat der C++-Standard Spezialfälle enthalten. So darf der Compiler den Copy-Konstruktor in einem return-Ausdruck wegoptimieren und das Objekt direkt in der Zielspeicherstelle anlegen, und bei std::auto_ptr sorgt eine komplizierte Implementierung auf Bibliotheksebene für den gewünschten Effekt.

C++11 stellt nun Sprachmittel bereit, die das Verschieben zu einer Erste-Klasse-Operation werden lässt. Neben Copy-Konstruktor und -Zuweisungsoperator kann der Klassenautor nun Move-Konstruktoren und -Zuweisungsoperatoren implementieren.

class Movable {
MovablePrivate * d;
public:
// ...
// copy semantics:
Movable( const Movable & other )
: d( other.d )
{
if ( d ) d->ref();
}
Movable & operator=( const Movable & other ) {
if ( other.d ) other.d->ref();
if ( d ) d->unref();
d = other.d;
return *this;
}
// move semantics:
Movable( Movable && other )
: d( other.d )
{
other.d = 0;
}
Movable & operator=( Movable && other ) {
std::swap( d, other.d );
return *this;
}
// ...
};

Möglich machen das sogenannte Rvalue-Referenzen (&&). Sie binden im Gegensatz zu klassischen Referenzen (&) nicht nur an Lvalues, sondern eben auch an Rvalues. Lvalues sind Ausdrücke, denen man etwas zuweisen kann (sie können links vom Zuweisungsoperator stehen), Rvalues alle anderen (sie können nur rechts vom Zuweisungsoperator stehen).

Im Beispiel vereinfacht sich der Move-Zuweisungsoperator dadurch, dass der Entwickler nur die d-Zeiger tauscht. Das ist deshalb legitim, weil er eine Verschiebung implementiert, die other nicht in einem sinnvollen Zustand belassen muss (nur in einem konsistenten). Das ist effizienter als der Copy-Zuweisungsoperator, der beide d-Zeiger referenzieren muss und mehrere Sprünge enthält.

Zur Umwandlung von Lvalues in Rvalue-Referenzen stellt der Standard die std::move()-Funktion zur Verfügung.

Variadische Funktions- und Klassen-Templates vereinfachen den Entwurf von Klassen
wie std::tuple oder von Funktionen wie einem C++-printf. Sie sind daher in erster Linie für Bibliotheksentwickler und weniger für Anwendungsentwickler interessant.

Als Abschluss der Top 11 seien noch Template-Typedefs genannt. Anders als die vielleicht erwartete Syntax

template <typename T>
typedef std::vector<T,MyAlloc<T>> MyAllocVector;

einzuführen, hat sich das C++-Komitee entschieden, typedef gleich durch eine einfachere Syntax zu ersetzen. Jeder, der schon mal einen Funktionszeiger in C/C++ deklarieren musste (und insbesondere alle, die das bisher noch nicht mussten), werden die neue Syntax zu schätzen wissen:

template <typename T>
using MyAllocVector<T> = std::vector<T,MyAlloc<T>>;

Damit kann man nun auch schreiben:

using myint32 = int;
using myfunptr = int(*)( double );
using mymemfunptr = int(Class::*)( double );

Die äquivalenten Deklarationen mit typedef seien dem geneigten Leser hiermit zur Abschreckung als Übung gestellt.