Über unsMediaKontaktImpressum
Ulrich Breymann 30. Juni 2015

Neuerungen in C++14

Die Anforderungen an C++ sind gewachsen, auch zeigte sich, dass manches fehlte und anderes überflüssig oder fehlerhaft war. Das C++-Standardkomitee hat kontinuierlich an der Verbesserung von C++ gearbeitet, sodass zuletzt 2011 und 2014 neue Versionen des Standards herausgegeben wurden, kurz C++11 und C++14 genannt. Insbesondere C++11 hat konzeptionell wesentliche Änderungen gebracht, darunter die Move-Semantik zur Performanzverbesserung und andere, über die ich kurz berichtet hatte [1]. C++14 hat gegenüber C++11 wenige Änderungen. Es handelt sich um Korrekturen und Ergänzungen. Die Änderungen erleichtern die Softwareentwicklung, sind aber nicht von so konzeptioneller Art wie der Sprung von C++03 nach C++11. C++14 wurde im August 2014 von der zuständigen ISO/IEC-Arbeitsgruppe JTC1/SC22/WG21 verabschiedet und am 15.12.2014 von der ISO veröffentlicht.

Im Folgenden stelle ich eine Übersicht der meisten Änderungen vor. Dabei setze ich voraus, dass Sie im Großen und Ganzen mit C++11 vertraut sind, und dass Sie Verständnis für die vielleicht etwas trockene Aufzählung haben...

Binäre Literale und Zifferntrennzeichen

Binäre Literale sind nun mit 0b oder 0B möglich. Zur besseren Lesbarkeit von Programmen sind Zifferntrennzeichen erlaubt, zum Beispiel 0B1100’1100’0110, 0xabc'def' oder 1'000'000.

constexpr-Funktionen

constexpr-Funktionen können schon zur Compilierzeit berechnet werden. Eine constexpr-Funktion gibt keinen constexpr-Wert zurück, wenn sich das übergebene Argument nicht auf ein Literal zurückführen lässt. Im Vergleich zu C++11 werden in C++14 einige Einschränkungen fallengelassen. constexpr-Funktionen können jetzt Deklarationen und mehrere return-Anweisungen enthalten, auch if, switch und Schleifen sind erlaubt. Rekursion ist möglich. In C++11 waren alle nicht-statischen constexpr-Member-Funktionen automatisch const, d. h. sie konnten ein Objekt nicht verändern. Das gilt in C++14 nicht mehr, weil es in einer constexpr-Funktion Ausdrücke geben darf, die ein Objekt verändern, wenn die Lebensdauer des Objekts innerhalb der Funktion beginnt. Gegebenenfalls muss also eine constexpr-Member-Funktion als const gekennzeichnet werden. Ein Beispiel: Gegeben sei eine Klasse Kreis, in der der Konstruktor und sämtliche Member-Funktionen constexpr sind. Dann könnte man eine globale Funktion schreiben, die einen skalierten Kreis zurückgibt, etwa:

constexpr Kreis skalieren(const Kreis& k, double faktor) {
   Kreis temp = k;
   temp.setRadius(k.getRadius() * faktor);
   return temp;
}

Diese Funktion kann in C++14 für constexpr-Parameter zur Compilierzeit berechnet werden, auch wenn setRadius(...) definitiv nicht const sein kann. Mit C++11 ist das nicht möglich.

Template-Konstante

Neu sind auch Template-Konstanten, die so deklariert werden können: template<typename T> constexpr T pi = 3.14159265358979323846264338328L; Die Instanzierung zur Compilationszeit ergibt eine Zahl mit dem angegebenen Datentyp. pi<float> ist also eine float-Zahl mit der entsprechenden Genauigkeit.

auto als Rückgabetyp von Funktionen

Es gibt mit dem Rückgabetyp auto eine einfachere Variante zur automatischen Typbestimmung einer Funktion, die ohne decltype auskommt. Beispiel:

auto func1(const Klasse& obj) { 
   auto x = obj.getX();
   // Berechnungen mit x weggelassen ...
   return x;
}

Typermittlung bei der Initialisierung mit decltype(auto)

Die Typermittlung bei der Initialisierung mit auto ist meistens nicht sinnvoll, weil mit auto sowohl eine const- wie auch eine Referenzeigenschaft ignoriert wird. decltype(auto) schafft Abhilfe:

int i = 1;
int& r = i;
auto ar = r;                                // int, nicht: int&
decltype(r) dr = r;                         // int& C++11/14
decltype(auto) dra = r;                     // int& C++14
const int konstante = 42;
auto ak = konstante;                        // int, nicht: const int
decltype(konstante) dk = konstante;         // const int, C++11/14
decltype(auto) dka = konstante;             // const int, C++14

Für die Rückgabe einer Referenz bei einer Funktion ist entsprechend decltype(auto) statt auto zu verwenden.

quoted-Manipulator

Bei der Verarbeitung von Strings mit Leer- und Anführungszeichen ist die Ein- und Ausgabe nicht symmetrisch. Das heißt, dass ein eingelesener String nicht genau so wieder ausgegeben wird. Der Grund liegt im Verhalten der <<- und >>-Operatoren. Der Eingabeoperator liest bis zum nächsten Zwischenraumzeichen. Ein auszugebendes Anführungszeichen muss mit einem Backslash \ maskiert werden. Das führt zu Problemen bei der automatisierten Verabeitung von Text, wenn der zu analysierende Text genau so (oder nur wenig verändert) wieder ausgegeben werden soll. Ein Beispiel:

std::string original =  "abc \"def\" ijk";
std::cout << original << '\n';

Die Ausgabe ist abc "def" ijk. Im Vergleich zum Original fehlen die Backslashs. Bei der Eingabe ist die Veränderung noch gravierender: Wird "abc \"def\" ijk" eingegeben, ist die Ausgabe nur "abc:

std::string text;
std::cin >> text;
std::cout << text << '\n';

Um die Verarbeitung von solchen Strings zu erleichtern, wurde der quoted-Manipulator eingeführt. Die Schnittstelle ist:

quoted(const char* s, char delim=char('"'), char escape=char('\\'))

s kann auch vom Typ string& oder const string& sein. Anstelle des üblichen Anführungszeichens kann ein anderes Zeichen gewählt werden; das gilt entsprechend für den Backslash. Mit dem quoted-Manipulator bleiben Anführungszeichen und Backslashs erhalten.

// Eingabe 
std::cin >> std::quoted(text);             // "abc \"def\" ijk"
std::cout << text << '\n';                 // abc "def" ijk
std::cout << std::quoted(text) << '\n';    // "abc \"def\" ijk"

Lambda-Funktionen

Im Folgenden sollen die Elemente eines Vektors v<double> nach dem Absolutbetrag sortiert werden:

sort(v.begin(), v.end(),[](double x, double y) { return abs(x) < abs(y); });

C++14 erlaubt die direkte Typermittlung mit auto:

sort(v.begin(), v.end(),[](auto x, auto y) { return abs(x) < abs(y); });

Ferner können Lambda-Funktionen in C++14 mehrere return-Anweisungen enthalten, sofern der Rückgabetyp gleich ist. Rekursion ist erlaubt.

Seit C++14 können funktionslokale Konstanten in den eckigen Klammern initialisiert werden. Im folgenden Kontext einer Funktion berechne() (deren Details wie auch die von summe und v hier nicht genannt sind) ist es die Größe mittelwert:

auto varianz = berechne(v, 0.0, [mittelwert = summe/v.size()](auto sum, auto w) { 
                           auto diff = w - mittelwert;
                           return sum + diff*diff;}
      ) /v.size();

Benutzerdefinierte Literale

C++14 stellt die folgenden Suffixe zur Verfügung: s für Strings und h, min, s, ms, us und ns für Zeitdauern und i für den Imaginärteil komplexer Zahlen:

using namespace std::literals::string_literals;
std::string s1 = "123"s + "abc"s;

Ohne das Suffix, das den Operator string operator"" s(const char*, size_t) des Standards aufruft, wäre die Verkettung nicht möglich.

#include <chrono>
auto dauerInStunden = 10h;

#include <complex>
complex imaginaer {3.7i};     // = (0.0, 3.7)

Statt i ist auch il (für complex<long double>) oder if (für complex<float>) einsetzbar.

Bei benutzerdefinierten Literalen ist eine Auswertung zur Compilationszeit möglich. Das heißt, dass ein fehlerhaftes benutzerdefiniertes Literal schon zur Compilationszeit entdeckt werden kann. Im Beispiel sei _b ein Suffix eines selbstdefinierten Operators zur Darstellung binärer Zahlen (die Möglichkeit der Darstellung mit 0b... werde ignoriert):

constexpr auto x1 = 1001_b;  // ok
constexpr auto x2 = 2001_b;  // 2 ist keine Binärziffer: Fehlermeldung!

Compiler: clang++ 3.6. Leider gibt es die Fehlermeldung nicht vom g++ 5.1.

Auswahl eines Tupelelements mit dem Typ

Der Aufruf get<n>(einTupel) gibt das n-te Element des Tupels einTupel zurück. C++14 ermöglicht die Rückgabe eines Tupel-Elements durch Angabe des Typs, also etwa get<int>(einTupel). Voraussetzung ist, dass nur genau ein Element diesen Typ hat.

Template Alias X_t

In der Standardbibliothek gibt es viele Klassen, die ein Attribut type haben. Seit C++14 gilt für alle Typnamen X<...>::type, dass es ein Template-Alias X_t<...> gibt.

Kleinere Ergänzungen/Korrekturen

begin()/end()

Die in C++11 noch fehlenden non-member-Funktionen std::cbegin(), std::rbegin() und std::crbegin() einschließlich der entsprechenden end()-Funktionen wurden in C++14 aufgenommen.

Umbenennung

shared_mutex wurde in shared_timed_mutex umbenannt.

make_unique()

C++11 ermöglicht mit make_shared() die einfache Erzeugung eines shared_ptr-Objekts. Für unique_ptr fehlt diese Möglichkeit in C++11. C++14 stellt make_unique() zur Verfügung.

Algorithmen

Einge Algorithmen verlangen als Parameter ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2. Falls last2 nicht explizit angegeben wird, wird last2 == first2 + (last1 - first1) angenommen. Falls der zweite Bereich aber weniger Elemente hat, ist das Ergebnis fehlerhaft. Deswegen gibt es seit C++14 diese zusätzlichen Varianten, die auf jeden Fall false zurückgeben, wenn die beiden Bereiche unterschiedlich groß sind, gezeigt am Beispiel is_permutation():

template<class ForwardIterator1, class ForwardIterator2>
bool is_permutation(ForwardIterator1 first1, ForwardIterator1 last1,
                    ForwardIterator2 first2, ForwardIterator2 last2);

template<class ForwardIterator1, class ForwardIterator2, class BinaryPredicate>
bool is_permutation(ForwardIterator1 first1, ForwardIterator1 last1,
                    ForwardIterator2 first2, ForwardIterator2 last2,
                    BinaryPredicate pred);

Betroffen sind auch die Algorithmen mismatch() und equal().

Zufallszahlen: rand() wird nicht empfohlen

rand() ist eine C-Funktion. Sie gibt eine etwa gleichverteilte Pseudozufallszahl zwischen 0 und RAND_MAX zurück. Seit C++14 wird rand() nicht mehr empfohlen, weil die damit verbundene Verteilung aus mathematischer Sicht einer wirklich zufälligen Verteilung nicht so nahe kommt wie andere von C++14 angebotene Verteilungen. Auch kann rand() kein Bereich übergeben werden, sodass eine eigene Umrechnung notwendig wäre.

Wie es weitergeht

Der Standard wird weiter modernisiert. Wie der Zeitplan [2] zeigt, ist die nächste Aktualisierung für 2017 geplant.

Quellen

[1] Ulrich Breymann "C++11-Standard: Was ist neu?", Informatik Aktuell
[2] C++-Zeitplan

© Ulrich Breymann 2015. Quelle: Der C++-Programmierer, Hanser Verlag, 4. Auflage 2015.

Autor

Prof. Dr. Ulrich Breymann

Prof. Dr. Ulrich Breymann lehrte Informatik an der Hochschule Bremen. Er engagierte sich im DIN-Arbeitskreis zur ersten Standardisierung von C++ und ist ein renommierter Autor zum Thema C++. Aus seiner Tätigkeit in Industrie und...
>> Weiterlesen
botMessage_toctoc_comments_9210