C++11, C++14 und C++17. Ich denke, du siehst das Muster. Im Laufe des verbleibenden Jahres erhalten wir einen neuen C++ Standard. Im März 2017 erlangte C++17 die Draft International Standard Stufe. Bevor ich tiefer in die Details eintauche, möchte ich einen Überblick geben.
Zuerst ein Blick auf das große Bild.
Das große Bild
Was C++98 bis C++14 betrifft, habe ich nur die wichtigen Punkte aufgezählt. Aber halt, ein C++-Standard fehlt in meiner Aufzählung: C++03. Das ist Absicht, denn C++03 ist ein sehr kleiner C++-Standard. Eigentlich mehr eine Bugfix-Release zu C++98. Falls du C++ kennst, weißt du, dass der erste ISO Standard C++98 und C++11 große Standards waren. Das gilt nicht für C++14 und insbesondere C++03.
Daher habe ich natürlich die Frage: Ist C++17 ein großer oder ein kleiner Standard? Für mich ist der Antwort ziemlich einfach. C++17 liegt irgendwo zwischen C++14 und C++11. C++17 ist daher weder groß noch klein. Warum? Hier kommt meine kurze Antwort.
Überblick
C++17 hat viel anzubieten. Das gilt für die Kernsprache und für die Bibliothek. Lass uns zuerst auf die Kernsprache schauen.
Kernsprache
Fold Expressions
C++11 unterstützt Variadic Templates. Dies sind Template, die eine beliebige Anzahl an Argumenten annehmen können. Die beliebige Anzahl wird von einem Parameter Pack gehalten. Neu ist mit C++17, dass dieses Parameter Pack direkt mit einem binären Operator reduziert werden kann.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// foldExpressionAll.cpp #include <iostream> template<typename... Args> bool all(Args... args) { return (... && args); } int main(){ std::cout << std::boolalpha; std::cout << "all(): " << all() << std::endl; std::cout << "all(true): " << all(true) << std::endl; std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl; std::cout << std::endl; } |
Der binäre Operator in dem Beispiel ist das logische AND in Zeile 6. Hier kommt die Ausgabe des Programms.
Das ist alles, was ich über Fold Expressions zu sagen habe, da ich bereits einen Artikel zu Fold Expression geschrieben habe. Dort gibt es die Details.
Wir bleiben bei der Übersetzungszeit.
constexpr if
constexpr if erlaubt dir, Sourcecode bedingt zu übersetzen.
1 2 3 4 5 6 7 |
template <typename T> auto get_value(T t) { if constexpr (std::is_pointer_v<T>) return *t; // deduces return type to int for T = int* else return t; // deduces return type to int for T = int } |
Falls T ein Zeiger ist, dann wird der if Zweig in Zeile 3 übersetzt. Falls nicht, der else Zweig in Zeile 5. Zwei Punkte muss ich in dem Zusammenhang erwähnen. Die Funktion get_value besitzt zwei verschieden Rückgabetypen und beide Zweige der if Anweisungen müssen gültig sein.
Konsequenterweise können if und switch Anweisungen in C++17 das, was for Anweisungen schon lange können.
Initialisierer in if und switch Anweisungen
Du kannst deine Variable direkt in der if und switch Anweisungen initialisieren.
1 2 3 4 5 6 7 8 9 |
std::map<int,std::string> myMap; if (auto result = myMap.insert(value); result.second){ useResult(result.first); // ... } else{ // ... } // result is automatically destroyed |
Daher ist die Variable result innerhalb des if und else Zweigs der if Anweisung gültig. result verschmutzt aber nicht den umgebenden Bereich.
Falls du Initialisierer in if und switch Anweisungen zusammen mit Structured Binding Deklaration (structured binding declaration) verwendest, wird die C++ Syntax noch eleganter.
Structured Binding Deklaration
Dank Structured Binding kannst du std::tuple oder ein struct direkt an eine Variable binden. Daher kann ich mein letztes Beispiel weiter verbessern.
1 2 3 4 5 6 7 8 9 |
std::map<int,std::string> myMap; if (auto [iter, succeeded] = myMap.insert(value); succeeded) { useIter(iter); // ... } else{ // ... } iter and succeded are automatically be destroyed |
Der Ausdruck auto [iter, succeeded] in Zeile 3 erzeugt automatisch die zwei Variablen iter und succeeded. In Zeile 9 werden sie wieder gelöscht.
Das ist ein Feature, mit dem Programmieren weniger mühselig wird. Das gleiche gilt für die automatische Template Ableitung von Konstruktoren.
Template Ableitung für Konstruktoren
Ein Funktions-Template kann seine Typ-Parameter von seinen Funktions-Argumenten ableiten. Aber das war für eine spezielles Funktions-Template nicht möglich: Den Konstruktor eines Klassen-Templates. Mit C++17 ist dieser Satz einfach falsch. Der Konstruktor kann seine Typ-Parameter von seinen Konstruktor-Argumenten ableiten.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// templateArgumentDeduction.cpp #include <iostream> template <typename T> void showMe(const T& t){ std::cout << t << std::endl; } template <typename T> struct ShowMe{ ShowMe(const T& t){ std::cout << t << std::endl; } }; int main(){ std::cout << std::endl; showMe(5.5); // not showMe<double>(5.5); showMe(5); // not showMe<int>(5); ShowMe<double>(5.5); // with C++17: ShowMe(5.5); ShowMe<int>(5); // with C++17: ShowMe(5); std::cout << std::endl; } |
Zeile 21 und Zeile 22 konnte bereits der erste C++98-Standard übersetzen. Für die Zeilen 24 und 25 ist ein C++17 Compiler notwendig. Daher musst du keine eckigen Klammern mehr verwenden um ein Klassen-Template zu instanziieren.
Aber in C++17 geht es nicht nur um die Benutzerfreundlichkeit. Es geht auch um die Performanz.
Vermeidung von Kopieren
RVO steht für Return Value Optimisation und bedeutet, das der Compiler unnötige Copy-Operation entfernen kann. Was bisher ein Optimierungsschritt war, muss der Compiler in C++17 zusichern.
2 3 4 |
MyType func(){ return MyType{}; // no copy with C++17 } MyType myType = func(); // no copy with C++17 |
Zwei unnötige Copy-Operationen können in den paar Zeilen stattfinden. Der erste in Zeile 2 und die zweite in Zeile 4. Mit C++17 darf das nicht mehr passieren.
Falls der Rückgabewert einen Namen besitzt, nennen wir dieses Konstrukt NRVO. Ich denke, du ahnst es bereits. Das Akronym steht für Named Return Value Optimization.
1 2 3 4 5 |
MyType func(){ MyType myVal; return myVal; // one copy allowed } MyType myType = func(); // no copy with C++17 |
Der feine Unterschied ist, das der Compiler mit C++17 den Wert myValue kopieren darf. Aber in Zeile 5 findet definitiv kein kopieren statt.
Falls ein Feature nicht mehr notwendig ist oder seine Anwendung sehr fehleranfällig ist, sollte es entfernt werden. Das passiert in C++17 mit std::auto_ptr und Trigraphen.
auto_ptr und Trigraphen entfernt
auto_ptr
std::auto_ptr ist der erste Smart Pointer in C++. Sein Job ist, genau auf eine Ressource aufzupassen. Aber er besaß ein großes Problem. Wenn du einen std:auto_ptr kopierst, findet ein Move-Operation unter der Decke statt. Das ist der Grund, dass wir std::unique_ptr in C++11 als Ersatz erhielten. Ein std::unique_ptr kann nicht kopiert werden.
1 2 3 4 5 6 |
std::auto_ptr<int> ap1(new int(2011)); std::auto_ptr<int> ap2= ap1; // OK (1) std::unique_ptr<int> up1(new int(2011)); std::unique_ptr<int> up2= up1; // ERROR (2) std::unique_ptr<int> up3= std::move(up1); // OK (3) |
Trigraphen
Trigraphen sind drei Zeichen im Sourcecode, die wie ein Zeichen behandelt werden. Sie sind dann notwendig, wenn die Tastatur das gewünschte Zeichen nicht unterstützt.
Falls du unleserlichen Code in C++17 schreiben willst, ist C++17 vielleicht nicht mehr die Sprache deiner Wahl.
1 2 3 4 5 6 7 |
// trigraphs.cpp int main()??< ??(??)??<??>(); ??> |
Ich nehme an, du weist, was das Programm tut? Falls nicht, musst du die Trigraphen in ihre entsprechenden Zeichen übersetzen.
Falls du die Tabelle anwendest, löst sich das Rätsel auf. Das Programm repräsentiert eine Lambda-Funktion, die just-in-place ausgeführt wird.
1 2 3 4 5 6 7 |
// trigraphsLambda.cpp int main(){ []{}(); } |
Wie geht's weiter?
Das ist einfach. Im nächsten Artikel schreibe ich über die neuen Bibliotheken, die wir mit C++17 erhalten. Dies sind string_view, die parallele STL und die Dateisystem Bibliothek. Zusätzlich bekommen wir die neuen Datentypen std::any, std::optional, und std::variant.
Go to Leanpub/cpplibrary "What every professional C++ programmer should know about the C++ standard library". Hole dir dein E-Book. Unterstütze meinen Blog.
Weiterlesen...