C++11 kennt zwei neue Literale: Raw-String- und benutzerdefinierte Literale. Während Raw-String-Literale den Interpreter zum Beispiel davon abbringen, Backslashes in Zeichenketten zu interpretieren, schützen benutzerdefinierte Literale dereinst womöglich Nasa-Sonden vor dem Verglühen.
Laut seiner Missionsollte der Mars Climate Orbiter im Jahre 1999 das Klima auf dem roten Planeten erforschen. Doch statt 150 Kilometer über der Marsoberfläche in eine Umlaufbahn einzuschwenken, tauchte die Pannen-Sonde auf 57 Kilometer hinab, wo die Atmosphäre ihr den Garaus machte. Die Analyse des Vorfalls ergab einen Einheitenfehler [1] in der Software als Ursache. Kleiner Bug, größtmögliche Auswirkung.
Die in C++11 neu eingeführten benutzerdefinierten Literale hätten das Problem womöglich verhindert. Sie verknüpfen Zahlen mit Einheiten, um diese in arithmetischen Ausdrücken zu verwenden. Ebenfalls neu an Bord von C++11 sind Raw-String-Literale, die besonders Windows-Entwicklern die tägliche Arbeit erleichtern.
Klein und praktisch
C++-Entwickler von Windows-Programmen beklagen sich häufig, dass sie ihre Dateien nicht öffnen können. Der Grund: Windows verwendet den Backslash »\« für den Zugriff auf Unterverzeichnisse. Das Backslash-Zeichen leitet aber diverse Escape-Sequenzen [2] ein. So stehen etwa die Zeichensequenzen »\n« für einen Zeilenumbruch und »\t« für einen Tabulator. Daher muss der Entwickler das Backslash-Zeichen selbst durch ein weiteres Backslash-Zeichen maskieren, damit C++ es nicht interpretiert. Kompliziert? Es wird noch besser.
Um etwa den Text »C++« mit Hilfe regulärer Ausdrücke in C++ zu beschreiben, ist der verwirrende String »C\\+\\+« notwendig. Zum einen muss der Programmierer das Pluszeichen im regulären Ausdruck durch einen Backslash, zum anderen diesen Backslash selbst als Teil des Strings maskieren.
Diese Backslash-Krämpfe gehören zum Glück der Vergangenheit an, denn dank der Raw-String-Literale schreibt der Entwickler ein einleitendes »R”(« und schiebt noch die abschließende Zeichenkette »)”« hinterher. Innerhalb der Klammern interpretiert C++ die Backslash-Symbole nicht (Listing 1).
Listing 1
Raw-String-Literale
01 #include <iostream>
02 #include <string>
03
04 int main(){
05
06 std::cout << std::endl;
07
08 std::string nat="C:\temp\newFile.txt";
09 std::cout << nat << std::endl;
10
11 // including \t \n
12 std::string raw1= std::string(R"(C:\temp\newFile.txt)");
13 std::cout << "\n" << raw1 << std::endl;
14
15 // including \t \n and using delimiter
16 std::string raw2= std::string(R"TRENNER(C:\temp\newFile.txt)TRENNER");
17 std::cout << "\n" << raw2 << std::endl;
18
19 // raw string including "
20 std::string raw3= std::string(R"(a raw string including ")");
21 std::cout << "\n" << raw3 << std::endl;
22
23 std::cout << std::endl;
24
25 }
Ruft der Entwickler das Programm aus Listing 1 auf, zeigt sich schön der Effekt der Raw-String-Literale (Abbildung 1). Die Zeichenkette »nat« in Zeile 8 verwendet unmaskierte Zeichen für einen Tabulator und einen Zeilenumbruch, mit ihr lässt sich keine Datei unter Windows öffnen. Anders sieht es mit den Strings »raw1« und »raw3« in den Zeilen 12 und 20 aus, deren Zeichen C++11 nicht in Escape-Codes verwandelt, selbst wenn das Raw-String-Literal »raw3« ein Anführungszeichen enthält.
In der Zeile 16 des Listings erscheint mit »raw2« ein Sonderfall. Die dort vorhandene Zeichensequenz »TRENNER« ersetzt der Entwickler wahlweise durch andere Zeichen, maximal 16 an der Zahl, wobei auch Leerzeichen darunter sein dürfen. Auf diesem Wege kann er auch Raw-String-Literale definieren, welche die Zeichensequenz »”)« enthalten, die C++11 andernfalls als das Ende des Raw-String-Literals interpretieren würde.
Für Groß und Klein
Nicht immer ziehen Umrechnungsfehler gleich interplanetare Zwischenfälle nach sich, doch schon das alltägliche Umrechnen von Einheiten bietet eigene Herausforderungen (Abbildung 2). Die sollten mit modernem C++ der Vergangenheit angehören, weil es die benutzerdefinierten Literale ermöglichen, Built-in-Literale mit eigenen Suffixen zu verknüpfen. Eingebaute Literale sind Elemente, die für explizite Werte in einem Programm stehen, sei es eine Zahl, ein String oder auch eine Lambda-Funktion.
Bei benutzerdefinierten Literalen hängt C++ an das Built-in-Literal einen Unterstrich »_« sowie einen Suffix an, wobei Letzterer typischerweise aus Einheiten besteht. Beim benutzerdefinierten Literal »1010101_b« handelt es sich um eine boolesche Zahl, in »63_s« um eine Sekundenangabe, in »33_cent« um eine Geldmenge oder in »”Hallo”_i18n« um einen Internationalisierungs-String.
Aber genug der Theorie, Listing 2 zeigt das Ganze an einem praktischen Codebeispiel. Die Funktion des Programms ist schnell skizziert. Instanzen der Klasse »MyDistance« (Zeilen 5 bis 27) stehen für Distanzobjekte, die das Programm mit Einheiten wie Kilometer, Meter, Dezimeter und Zentimeter initialisiert und verrechnet. Bevor es aber an die Details der C++-Magie geht, ruht der Blick des Betrachters auf der »main()« -Funktion (Zeilen 47 bis 78), um ein Gesamtbild zu erhalten. Die Ausgabe des Programms liefert Abbildung 3.
Listing 2
Benutzerdefinierte Literale
01 #include <iostream>
02 #include <ostream>
03
04 namespace Distance{
05 class MyDistance{
06 public:
07 MyDistance(double i):m(i){}
08
09 friend MyDistance operator+(const MyDistance& a, const MyDistance& b){
10 return MyDistance(a.m + b.m);
11 }
12
13 friend MyDistance operator-(const MyDistance& a, const MyDistance& b){
14 return MyDistance(a.m - b.m);
15 }
16
17 friend MyDistance operator*(double m, const MyDistance& a){
18 return MyDistance(m*a.m);
19 }
20
21 friend std::ostream& operator<<(std::ostream &out, const MyDistance& myDist){
22 out << myDist.m << " m";
23 return out;
24 }
25 private:
26 double m;
27 };
28
29 namespace Unit{
30 MyDistance operator "" _km(long double d){
31 return MyDistance(1000*d);
32 }
33 MyDistance operator "" _m(long double m){
34 return MyDistance(m);
35 }
36 MyDistance operator "" _dm(long double d){
37 return MyDistance(d/10);
38 }
39 MyDistance operator "" _cm(long double c){
40 return MyDistance(c/100);
41 }
42 }
43 }
44
45 using namespace Distance::Unit;
46
47 int main(){
48
49 std:: cout << std::endl;
50
51 std::cout << "1.0_km: " << 1.0_km << std::endl;
52 std::cout << "1.0_m: " << 1.0_m << std::endl;
53 std::cout << "1.0_dm: " << 1.0_dm << std::endl;
54 std::cout << "1.0_cm: " << 1.0_cm << std::endl;
55
56 std::cout << std::endl;
57
58 std::cout << "0.001 * 1.0_km: " << 0.001 * 1.0_km << std::endl;
59 std::cout << "10 * 1_dm: " << 10 * 1.0_dm << std::endl;
60 std::cout << "100 * 1.0cm: " << 100 * 1.0_cm << std::endl;
61
62 std::cout << std::endl;
63 std::cout << "1.0_km + 2.0_dm + 3.0_dm + 4.0_cm: " << 1.0_km + 2.0_dm + 3.0_dm + 4.0_cm << std::endl;
64 std::cout << std::endl;
65
66 Distance::MyDistance work= 63.0_km;
67 Distance::MyDistance workPerDay= 2 * work;
68 Distance::MyDistance abbrevationToWork= 5400.0_m;
69 Distance::MyDistance workout= 2 * 1600.0_m;
70 Distance::MyDistance shopping= 2 * 1200.0_m;
71
72 Distance::MyDistance myDistancePerWeek= 4 * workPerDay - 3 * abbrevationToWork + workout + shopping;
73
74 std::cout << "4 * workPerDay - 3 * abbrevationToWork + workout + shopping: " << myDistancePerWeek;
75
76 std::cout << "\n\n";
77
78 }
Zunächst gibt es in den Zeilen 51 bis 54 die Distanzen in den unterstützten Einheiten aus. Zugleich normiert es diese in den Zeilen 31, 34, 37 und 40 explizit auf Meter, da »MyDistance« alle Werte in dieser Einheit speichert (Zeile 25). Die Zeilen 58 bis 60 sind schon interessanter, findet hier doch eine erste elementare Arithmetik statt. Wie das Beispiel zeigt, hat der Autor sein Einmaleins gelernt, denn der tausendste Teil eines Kilometers ist genau ein Meter.
Arithmetisch geht es auch in Zeile 63 weiter, die ein paar Einheiten miteinander verrechnet. Am informativsten ist aber sicherlich das Objekt »myDistancePerWeek« in Zeile 72: Die Instanz der Klasse »MyDistance« enthält die Entfernung, die der Autor des Artikels pro Woche mit dem Auto zurücklegen muss. Die Strecke summiert sich auf rund 500 Kilometer, bestehend im Wesentlichen aus vier Fahrten zur Arbeit, reduziert um drei Abkürzungen. Dank der entsprechenden Instanzen vom Typ »MyDistance« in den Zeilen 66 bis 70 bleibt der verwendete Code gut lesbar.
Normative Kraft
Zur oben angekündigten C++-Magie: Ein benutzerdefiniertes Literal wie »1.0_km« bildet der Compiler auf den so genannten Literal Operator ab. Im Code geschieht dies konkret in Zeile 30, der zugehörige Operator heißt »operator “” _km(long double d)« . Der Literal Operator stellt den Fließkommawert »1.0« bereit, den der Funktionskörper verwendet, um ein »MyDistance« -Objekt zu erzeugen und es als Ergebnis zurückzuliefern. In diesem Prozess normiert die Software automatisch auf die Einheit Meter: »MyDistance(1000*d);« . Entsprechende Literal Operator stehen für die Suffixe »_m« (Zeile 33) , »_dm« (Zeile 36) sowie »_cm« (Zeile 39) bereit.
Wichtig beim Literal Operator: Zwischen den doppelten Anführungszeichen »””« und dem Suffix »_km« verlangt die C++-Syntax ein Leerzeichen. Der Unterstrich beim Suffix ist streng genommen zwar nicht notwendig. Entwickler sollten es aber dennoch verwenden, da C++ Suffixe ohne Unterstrich für die Kernsprache reserviert.
Wie geht nun der Compiler mit einem Ausdruck der Form »1.0_km + 2.0_dm« um? Im ersten Schritt erzeugt das Programm den Ausdruck »MyDistance(1000) + 2.0_dm« . Im zweiten Schritt schlägt der überladene »+« -Operator in Zeile 9 zu und addiert die beiden Werte, nachdem er »2.0_dm« erfolgreich in »MyDistance(0.2)« konvertiert hat. Er liefert das Ergebnis als neues »MyDistance« -Objekt zurück: »MyDistance(1000.2)« . Der überladene Ausgabeoperator in Zeile 21 gibt die »MyDistance« -Objekte aus.
Ein scharfer Blick bringt Licht ins Dunkel: Die Klasse besteht lediglich aus einem Konstruktor (Zeile 7), der notwendig ist, um die private Instanzvariable »m« (Zeile 26) zu initialisieren. Damit die Operatoren auf die private Variable »m« (Zeile 26) zugreifen, deklariert der Entwickler alle überladenen Operatoren als freie Funktionen und Freunde (»friend« ) der Klasse »MyDistance friend« . Die C++-Magie ist hiermit zwar erklärt, ein paar Details rund um die benutzerdefinierten Literale fehlen aber noch.
Roh oder gekocht
C++ unterstützt benutzerdefinierte Literale für natürliche Zahlen, Fließkommazahlen, Zeichen und C-Strings. Bei Letzteren bildet der Wert des benutzerdefinierten Literals ein Paar, das aus dem C-String und seiner Länge besteht. Für natürliche Zahlen und Fließkommazahlen gilt indes eine Besonderheit. Für sie erlaubt die C++-Laufzeit benutzerdefinierte Literale in den beiden Formen Raw und Cooked. In der Cooked-Form (Listing 2) nimmt der Literal-Operator seine Argumente als »unsigned long long int« entgegen, wenn es sich um eine natürliche Zahl handelt. Fließkommazahlen interpretiert er hingegen als »long double« -Wert.
Im Gegensatz dazu steht der Wert Raw als »const char*« bereit. Ein Cooked-Literal-Operator »operator “” _km(long double)« besitzt im Rohzustand (Raw) die Form »operator “” _km(const char*)« . Bringt ein Programm sowohl einen Raw- als auch einen Cooked-Literal-Operator ins Spiel, erhält letzterer den Vorrang vor ersterem.
Mahlzeit!
Mit der Version C++14 führt C++ neue Built-in-Literale für binäre und komplexe Zahlen und Zeitwerte ein. So lassen sich 300 Sekunden und 0,25 Stunden ohne C++-Magie direkt addieren und in der gewünschten Einheit darstellen.
Einen feinen Unterschied gibt es zwischen den Built-in- und den benutzerdefinierten Literale allerdings: Erstere besitzen keinen Unterstrich, um Namenskollisionen zu vermeiden (siehe auch Kasten “Namensräume für benutzerdefinierte Literale”). Alle Details zu den neuen Built-in-Literalen sowie einige praktische Anwendungsbeispiele, die dabei helfen, anspruchsvolle Zeitausdrücke zu berechnen, liefert der Artikel unter [3].
Namensräume für benutzerdefinierte Literale
Da die Suffixe, die für Einheiten stehen, nicht selten sehr kurze und typische Bezeichner sind, ist die Gefahr einer Namenskollision relativ hoch. Daher sollte ein Entwickler diese in einem Namensraum kapseln und in den globalen Namensraum importieren. Dies geschieht in der Zeile 45 (Listing 2) mit dem Ausdruck »using namespace Distance::Unit;« .
Ausblick
Benutzerdefinierte Literale helfen vor allem dabei, die Built-in-Literale um eine eigene, zusätzliche Semantik zu ergänzen. Entwickler verstehen Ausdrücke wie »25.0_km + 5_m« unmittelbar beim Lesen im Code, der Compiler unterscheidet mit ihrer Hilfe Äpfel und Birnen voneinander. Die strenge Typisierung findet zur Übersetzungszeit statt und beeinflusst das Programm nicht zur Laufzeit.
In diesem Übersetzungsprozess agiert auch die neue Type-Traits-Bibliothek. Sie kann Typ-Informationen bestimmen, Typen vergleichen oder auch modifizieren. Damit ist die Bibliothek neben den benutzerdefinierten Literalen ein weiterer Pflasterstein, um C++ Typ-sicher zu machen. Setzt der Entwickler sie zusammen mit dem Ausdruck »static_assert« ein, kann er die Typ-Information der Type-Traits-Bibliothek als Zusicherungen an den Compiler formulieren, die dieser automatisch überprüft. Grund genug, das Zusammenspiel von »static_assert« und der Type-Traits-Bibliothek im nächsten Artikel unter die Lupe zu nehmen.
Infos
- Mars Climate Orbiter: http://de.wikipedia.org/wiki/Mars_Climate_Orbiter
- Escape-Sequenzen: http://de.wikipedia.org/wiki/Escape-Sequenz
- Rainer Grimm, “C++11 + 3 = C++14”: Linux-Magazin 04/2014, S. 102









