Aus Linux-Magazin 08/2016

Modernes C++ in der Praxis – Folge 29

Der neue Standard C++17 kündigt sich für 2017 an. Er wird zwar einige großartige Features wie eine Bibliothek für das Dateisystem mitbringen, doch andere lang ersehnte Funktionen fehlen wohl weiterhin.

Die Erwartungen der C++-Community, dass sie 2017 eine ähnlich bedeutende Release erleben kann wie 2011, sind wohl überzogen. Auf Features wie Concepts Lite, die Ranges-Bibliothek oder auch verbesserte Futures wird sie wahrscheinlich noch drei weitere Jahre warten müssen, also bis 2020. Dennoch kündigt sich mit C++17 der dritte große C++-Standard nach C++98 und C++11 an.

Das große Ganze

Abbildung 1 zeigt, was sich C++-Entwickler nach aktueller Lage für 2017 und 2020 erhoffen dürfen. Der Einfachheit halber verzichtet die Grafik auf die weniger bedeutenden C++-Standards C++03 und C++14.

Abbildung 1: Zwei umfangreiche C++-Standards sollen 2017 und 2020 erscheinen.

Abbildung 1: Zwei umfangreiche C++-Standards sollen 2017 und 2020 erscheinen.

In der Ferne: C++20

Zeitlich noch etwas weiter weg ist die 2020er Release des C++Standards. Basierend auf der Boost.Asio-Bibliothek [1] von Christopher Kohlhoff soll C++ mit ihm eine asynchrone, plattformunabhängige Bibliothek zur Netzwerk- und I/O-Programmierung erhalten.

Transactional Memory

Außerdem kündigen sich viele sehr interessante Verbesserungen rund um Multithreading an. Eine Studygroup im C++-Standardisierungskomitee beschäftigt sich mit Transactional Memory [2]. Sie wendet dabei, vereinfacht gesagt, die Transaktionsidee aus der Datenbanktheorie auf Code-Abschnitte an.

Dabei folgt ein als Transaktion ausgezeichneter Codebereich den drei ersten Buchstaben des ACID-Idioms. C++ führt ihn atomar (Atomicity) und isoliert (Isolation) von seiner Umgebung aus, zugleich befindet er sich stets in einem konsistenten Zustand (Consistency). Die vierte Eigenschaft, die Dauerhaftigkeit (Durability), trifft auf jene Code-Abschnitte hingegen nicht zu.

Die entscheidende Idee einer als Transactional Memory ausgezeichneten Codesequenz besteht jedoch darin, dass ein Programm diesen Abschnitt auf Verdacht (optimistisch) ausführen kann, ohne ihn mit anderen Threads zu synchronisieren. Nach dem Ende der Transaktion veröffentlicht es sein Ergebnis aber nur, wenn die Ausgangsbedingungen noch zutreffen. Gelten sie nicht mehr, verwirft es das Ergebnis der Transaktion und führt sie automatisch neu aus.

Während C++ Code-Abschnitte, die maximal ein Thread betreten darf, gewöhnlich mit Hilfe von Mutexen sperrt, verriegelt das Programm die Codesequenz bei dieser Transaktion nicht, sondern verwirft gegebenenfalls das Ergebnis.

Arrangierte Zukunft

C++20 verspricht noch weitere Optimierungen. So kann der Entwickler Futures [3] künftig besser arrangieren. In der Folge führt der Code einen Future genau dann aus, wenn der vorhergehende fertig mit seiner Arbeit ist. Es wird aber auch möglich sein, einen Future zu starten, sobald einer oder alle einer Menge von Futures die Arbeit abgeschlossen haben. Dank dieser Möglichkeit erzeugt ein Entwickler deutlich einfacher auf mehrere Threads verteilte Arbeitsabläufe.

Concepts Lite

Eigentlich sollten die so genannten Concepts [4] bereits in C++11 landen. Als Concepts Lite habe sie es jedoch nicht einmal in C++17 geschafft. Vielmehr verdichten sich inzwischen die Hinweise, dass die C++-Macher Concepts Lite sogar erst in C++20 umsetzen. Für Entwickler bringen Concepts Lite gleich mehrere Vorteile mit:

  • Sie erlauben es, die Anforderungen an die Templates direkt als Teil des Interface zu formulieren.
  • Sie unterstützen das Überladen von Funktionen und die Spezialisierung von Klassen-Templates basierend auf den Anforderungen an die Templates.
  • Sie erzeugen deutlich verbesserte Fehlermeldungen, indem sie die Anforderungen an die Template-Parameter mit den aktuellen Template-Argumenten vergleichen.

Insgesamt will C++ mit Hilfe der angedachten Änderungen am Standard also den Umgang mit den Templates vereinfach. Aber nicht nur mit diesen.

Mit Spannung erwartet die C++-Community auch die Ranges-Bibliothek [5] von Eric Nieber. Sie soll ein komfortableres und ausgefeilteres Arbeiten mit Containern ermöglichen. Komfortabler, weil die Algorithmen der STL direkt auf den Containern agieren und keinen Anfangs- und End-Iterator benötigen. Ausgefeilter, weil C++20 mit der Range-Bibliothek eine Bedarfsauswertung, eine deutlich verbesserte Funktionskomposition sowie eine Range Comprehension erhält, die der aus Haskell oder Python bekannten List Comprehension ähnelt.

In der Nähe: C++17

Wandert der Blick in die nähere Zukunft, bleibt er aber erst mal an C++17 hängen. Zunächst zur Kernsprache. C++11 kennt die im Linux-Magazin bereits vorgestellten Variadic Templates [6]. Die können eine beliebige Anzahl von Template-Parametern besitzen, die im Parameter-Pack stecken. Neu in C++17 soll sein, dass die Programmiersprache ein Parameter-Pack direkt über einen binären Operator reduzieren kann. Damit setzt ein Entwickler die aus Haskell bekannten »fold()« -Funktionen, die eine Liste sukzessive auf einen Wert reduzieren, direkt in C++ um (Listing 1).

Listing 1

Variadic Templates mit »fold()«-Support

01 #include <iostream>
02
03 bool allVar(){
04   return true;
05 }
06
07 template<typename T, typename ...Ts>
08 bool allVar(T t, Ts ... ts){
09   return t && allVar(ts...);
10 }
11
12 template<typename... Args>
13 bool all(Args... args) { return (... && args); }
14
15 int main(){
16
17   std::cout << std::boolalpha;
18
19   std::cout << "allVar(): " << allVar() << std::endl;
20   std::cout << "all(): " << all() << std::endl;
21
22   std::cout << "allVar(true): " << allVar(true) << std::endl;
23   std::cout << "all(true): " << all(true) << std::endl;
24
25   std::cout << "allVar(true, true, true, false): " << allVar(true, true, true, false) << std::endl;
26   std::cout << "all(true, true, true, false): " << all(true, true, true, false) << std::endl;
27
28 }

Die beiden Funktions-Templates »allVar()« und »all()« geben zur Übersetzungszeit genau dann den booleschen Wert »true« zurück, wenn alle Argumente wahr sind. Während »allVar()« die herkömmlichen Variadic Templates anwendet, kombiniert »all()« diese mit Fold Expressions.

Zuerst zu »allVar()« . Die Variadic Templates verwenden Rekursion, um ihre Argumente zu evaluieren. So setzt die Funktion »allVar()« in Zeile 3 von Listing1 Randbedingungen für den Fall, dass das Parameter-Pack leer ist.

Die eigentliche Rekursion findet im Funktions-Template »allVar()« in Zeile 7 statt. Die drei aufeinanderfolgenden Punkte (auch Ellipse genannt) definieren das Parameter-Pack, das zwei Operationen erlaubt. Das Programm kann es packen und entpacken. Zeile 7 packt das Parameter-Pack, die Zeilen 8 und 9 entpacken es. Dabei verlangt die Zeile 9 besondere Aufmerksamkeit. Sie verknüpft über den binären Operator »&&« den Kopf des Parameter-Packs »t« mit dem Rest des Parameter-Packs »ts« . Der Aufruf »allVar(ts…)« stößt dabei die Rekursion an. Er enthält ein Parameter-Pack, das die Funktion nun sukzessive um das erste Element reduziert.

Listing 2

Fold Expressions mit Defaultwert

01 #include <iostream>
02
03 template<typename... Args>
04 bool allLeft(Args... args) { return (true && ... && args); }
05
06 template<typename... Args>
07 bool allRight(Args... args) { return (args && ... && true); }
08
09 int main(){
10
11   std::cout << std::boolalpha;
12
13   std::cout << "allLeft(): " << allLeft() << std::endl;
14   std::cout << "allRight(): " << allRight() << std::endl;
15
16   std::cout << "allLeft(true): " << allLeft(true) << std::endl;
17   std::cout << "allRight(true): " << allRight(true) << std::endl;
18
19
20   std::cout << "allLeft(true, true, true, false): " << allLeft(true, true, true, false) << std::endl;
21   std::cout << "allRight(true, true, true, false): " << allRight(true, true, true, false) << std::endl;
22
23 }

Dieses Prozedere klappt mit C++17 deutlich einfacher. Hier reduziert ein binärer Operator das Parameter-Pack direkt. Abbildung 2 zeigt die beiden Algorithmen im Praxiseinsatz. Wer das Programm ausführen möchte, zieht am besten den Onliner-Compiler unter [7] heran. Die zugehörige Website dient zugleich als eine sehr vertrauenswürdige Quelle, um die Details zu den Fold Expressions [8] nachzulesen.

Abbildung 2: So sehen die Variadic Templates im Einsatz aus, einmal ohne und einmal mit den Fold Expressions.

Abbildung 2: So sehen die Variadic Templates im Einsatz aus, einmal ohne und einmal mit den Fold Expressions.

Variantenreich

Von den Fold Expressions gibt es zwei Varianten, die vier Formen bilden. Erstens können Fold Expression abhängig von ihrem binären Operator Standardwerte besitzen. Zweitens können sie das Parameter-Pack von links oder rechts beginnend prozessieren.

Ein kleiner, aber feiner Unterschied besteht dann auch zwischen den beiden Algorithmen »allVar()« und »all()« : Der zweite setzt als Standardwert für das leere Parameter-Pack »true« ein.

C++17 unterstützt insgesamt 32 binäre Operatoren in Fold Expressions [8]. Einige davon besitzen bereits Standardwerte (Tabelle 1). Für binäre Operatoren ohne Defaultwert muss der Entwickler einen Startwert angeben, für solche mit definiertem Defaultwert kann er es freiwillig tun.

Tabelle 1

Binäre Operatoren

Operator Symbol Defaultwert
Multiplikation * 1
Addition +
Bitweises AND & -1
Bitweises OR |
Logisches AND && true
Logisches OR || false
Komma-Operator , void()

Ob ein Programm das Parameter-Pack von links oder von rechts prozessiert, hängt davon ab, ob die Ellipse links oder rechts vom Parameter-Pack steht. Das Gleiche trifft auf Fold Expressions mit Startwert zu. Tabelle 2 stellt die vier Varianten vor. Der C++17-Standard verlangt von Fold Expressions mit einem Startwert, dass sie die gleichen binären Operatoren verwenden.

Tabelle 2

Fold Expressions

Fold Expression Beschreibung
… op pack Reduktion von links mit Operator »op«
pack op … Reduktion von rechts mit Operator »op«
init op … op pack Reduktion von links mit Operatoren »op« und Startwert »init«
pack op … op init Reduktion von rechts mit Operatoren »op« und Startwert »init«

Mit diesem Wissen versehen implementiert der Entwickler den Algorithmus »all()« aus Listing 2 mit einem Defaultwert und führt das Programm aus (Abbildung 3). Dabei prozessiert »allLeft()« seine Elemente von links, »allRight()« tut es von rechts. Die Ausgabe birgt keine Überraschung. Anstelle des GCC 6.1 in Abbildung 2 kommt diesmal Clang 3.8 zum Zuge.

Abbildung 3: Fold Expressions verwenden im Beispiel einen Defaultwert, als Compiler kommt hier Clang zum Einsatz.

Abbildung 3: Fold Expressions verwenden im Beispiel einen Defaultwert, als Compiler kommt hier Clang zum Einsatz.

Ein kleines Rätsel

Bevor dieser Teil der C++-Reihe zum Ende kommt, folgt dieses Mal noch ein Rätsel für C++-Insider. Listing 3 zeigt ein kleines C++11-Programm. Was tut dieses Programm? Nicht gerade viel, wie jedenfalls die Darstellung in Abbildung 4 suggeriert. Um die Lesbarkeit des Programms nicht unnötig zu erschweren, steht es hier schön gelayoutet. Kleiner Hinweis: Mit C++17 ist das Programm nicht mehr gültig.

Listing 3

Das kleine Rätsel

01 int main()??<
02
03   ??(??)??<??>();
04
05 ??>
Abbildung 4: Rätselhaft: Wie mag wohl der Code zu dieser Ausgabe aussehen?

Abbildung 4: Rätselhaft: Wie mag wohl der Code zu dieser Ausgabe aussehen?

Wie geht’s weiter?

Der nächste Artikel bringt die Lösung des Rätsels, taucht dann aber schon wieder direkt in die neuen Bibliotheken von C++17 ein. Er stellt unter anderem die Bibliothek für das Dateisystem, die parallele Version der Algorithmen der STL und die bereits aus Boost [9] bekannten Bibliotheken »optional« , »any« und »string_view« vor.

Infos

  1. Boost.Asio: http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio.html
  2. Transactional Memory: https://de.wikipedia.org/wiki/Transaktionaler_Speicher
  3. Rainer Grimm, “Alle im Einklang”: Linux-Magazin 10/12, S. 90
  4. C++-Concepts: http://en.cppreference.com/w/cpp/concept
  5. Ranges-Bibliothek: https://ericniebler.github.io/range-v3/
  6. Rainer Grimm, “Punktlandung”: Linux-Magazin 08/15, S. 88
  7. Referenz: http://en.cppreference.com
  8. Fold Expression: http://en.cppreference.com/w/cpp/language/fold
  9. Boost: http://www.boost.org

Der Autor

Rainer Grimm ist selbstständiger Trainer und Coach für modernes C++ und Python. Seine Bücher “C++11 für Programmierer”, “C++” und “C++11-Standardbibliothek” sind bei O’Reilly erschienen.

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben