Was gibt`s neues in der Bibliothek? Viel! Hier sind kurz und kompakt die Fakten. Wir bekommen einen std::string_view, parallele Algorithmen der Standard Template Library, ein Dateisystem Bibliothek und die drei neuen Datentypen std::any, std::optional und std::variant. Jetzt kommen die Details.
Unsere Reise beginnt mit std::string_view.
std::string_view
Ein std::string_view ist ein nicht-besitzende Referenz auf einen String. Diese repräsentiert eine Sequenz von Zeichen. Diese Sequenz von Zeichen kann ein C++-String oder ein C-String sein. In bekannter Manier bietet C++17 vier Typ-Synonyme für die zugrundeliegenden Zeichen-Typen an.
std::string_view std::basic_string_view<char> std::wstring_view std::basic_string_view<wchar_t> std::u16string_view std::basic_string_view<char16_t> std::u32string_view std::basic_string_view<char32_t>
Eine Frage bleibt natürlich bestehen. Warum benötigen wir std::string_view? Warum haben Google, LLVM und Bloomberg bereits eine Implementierung eines String-View? Die Antwort ist einfach. Es ist ziemlich billig einen std::string_view zu kopieren. std::string_view benötigt nur zwei Informationen: einen Zeiger auf die Sequenz von Zeichen und deren Länge. Wie du vermutlich schon vermutest, besteht std::string_view und sein drei Geschwister fast ausschließlich aus lesenden Operationen, die dem Interface des std::string folgen. Fast nur, den er erhält die zwei neuen Methoden remove_prefix und remove_suffix.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// string_view.cpp #include <iostream> #include <string> #include <experimental/string_view> int main(){ std::string str = " A lot of space"; std::experimental::string_view strView = str; strView.remove_prefix(std::min(strView.find_first_not_of(" "), strView.size())); std::cout << "str : " << str << std::endl << "strView : " << strView << std::endl; std::cout << std::endl; char arr[] = {'A',' ','l','o','t',' ','o','f',' ','s','p','a','c','e','\0', '\0', '\0'}; std::experimental::string_view strView2(arr, sizeof arr); auto trimPos = strView2.find('\0'); if(trimPos != strView2.npos) strView2.remove_suffix(strView2.size() - trimPos); std::cout << "arr : " << arr << ", size=" << sizeof arr << std::endl << "strView2: " << strView2 << ", size=" << strView2.size() << std::endl; } |
Das Programm sollte dich nicht überraschen. Die std::string_view's erhalten in Zeile 10 und 18 ihre Referenz auf den C++-String und die Sequenz von Zeichen. In Zeile 11 werden alle führenden Nicht-Leerzeichen (strView.find_first_not_of(" ")) und in Zeile 20 alle abschließenden "\0"-Zeichen (strView2.find('\0")) entfernt. Dank der Verwendung des Namensraums experimental kann ich das Programm bereits auf cppreference.com ausführen.
Nun wird es deutlich vertrauter.
Parallele Algorithmen der Standard Template Library
Nur ein paar Worte. 69 Algrotihmen der Standard Template Library (STL) werden in sequentieller, paralleler und parallel und vektorisierender Variante zu Verfügung stehen. Zusätzlich erhalten wir 8 neue Algorithmen. Hier sind die 69 neue Varianten (schwarz) und die 8 neuen (rot) Algorithmen:
Das war schon alles. Ich habe bereits einen Artikel zu den Parallelen Algorithmen der STL geschrieben.
In Gegensatz dazu, sollte die Dateisystembibliothek neu für dich sein.
Die Dateisystembibliothek
Die neue Dateisystembibliothek basiert auf boost::filesystem. Ihre Komponenten sind optional. Das bedeutet, dass nicht die ganze Funktionalität von std::filesytem auf jeder Plattform verfügbar sein muss. Zum Beispiel unterstützt FAT-32 keine symbolischen Links.
Die Bibliothek enthält die drei Konzepte Datei, Dateinamen und Pfad. Datei können Verzeichnisse, harte Links, symbolische Links oder reguläre Dateien sein. Pfade können absolut oder relative sein.
Es gibt ein mächtiges Interface für das Lesen und Manipulieren des Dateisystems. Verwende cppreference.com für die Details. Hier ist ein erster Eindruck.
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 30 |
// filesystem.cpp #include <fstream> #include <iostream> #include <string> #include <experimental/filesystem> namespace fs = std::experimental::filesystem; int main(){ std::cout << "Current path: " << fs::current_path() << std::endl; std::string dir= "sandbox/a/b"; fs::create_directories(dir); std::ofstream("sandbox/file1.txt"); fs::path symPath= fs::current_path() /= "sandbox"; symPath /= "syma"; fs::create_symlink("a", "symPath"); std::cout << "fs::is_directory(dir): " << fs::is_directory(dir) << std::endl; std::cout << "fs::exists(symPath): " << fs::exists(symPath) << std::endl; std::cout << "fs::symlink(symPath): " << fs::is_symlink(symPath) << std::endl; for(auto& p: fs::recursive_directory_iterator("sandbox")) std::cout << p << std::endl; // fs::remove_all("sandbox"); } |
fs::current_path() in Zeile 11 gibt den aktuellen Pfad zurück. Mit std::filesystem kannst du eine Verzeichnishierarchie erzeugen (Zeile 14). with std::filesystem. Zeile 18 schaut ein wenig ungewöhnlich aus. Der /= Operator ist für Pfade überladen. Daher kann ich direkt einen symbolischen Link in Zeile 18 erzeugen. Du kannst auch die Eigenschaften einer Datei abfragen (Zeile 21-23). Der Aufruf recursive_directory_iterator in Zeile 26 ist ziemlich mächtig. Du kannst ihn verwenden um rekursive Verzeichnisse zu traversieren. Klar, auf den Online-Compiler kann ich kein Verzeichnis löschen (Zeile 28).
Hier ist die Ausgabe des Programms:
Was haben die Datentypen std::any, std::optional und std::variant gemein. Sie sind alle von boost.
std::any
std::any ist die richtige Wahl, wenn du einen Container möchtest, der beliebige Werte besitzen kann. Beliebige Werte stimmt nicht ganz genau. std::any fordert, dass seine Werte kopierbar sein müssen. Hier ist ein einfaches Beispiel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// any.cpp #include <iostream> #include <string> #include <vector> #include <any> struct MyClass{}; int main(){ std::cout << std::boolalpha; std::vector<std::any> anyVec(true,2017,std::string("test"),3.14,MyClass()); std::cout << "std::any_cast<bool>anyVec[0]: " << std::any_cast<bool>(anyVec[0]); // true int myInt= std::any_cast<int>(anyVec[1]); std::cout << "myInt: " << myInt << std::endl; // 2017 std::cout << std::endl; std::cout << "anyVec[0].type().name(): " << anyVec[0].type().name(); // b std::cout << "anyVec[1].type().name(): " << anyVec[1].type().name(); // i } |
Die Ausgabe des Programms ist direkt im Sourcecode. In Zeile 14 definiere ich einen std::vector<std::any>. Um eines seiner Elemente zu erhalten, wende ich std::any_cast an. Falls du den falschen Typ anwendest, erhält du eine std::bad_any_cast Ausnahme. Weitere Details lassen sich wieder cppreferenc.com nachlesen. Du kannst aber auch auf einen weiteren Artikel von mir warten.
std::any kann Werte beliebigen Typs besitzen, std::optional kann einen Wert oder auch keinen Wert besitzen.
std::optional
Jetzt bin ich sehr kurz angebunden. In dem Artikel Monaden in C++ habe ich bereits die Monade std::optional vorgestellt.
Der dritte neue Datentyp von boost ist std::variant.
std::variant
Eine std::variant ist eine typsichere Union. Eine Instanz von std::variant besitzt einen Wert von einem seiner Typen. Der Typ kann keine Referenz, Array oder void sein. Eine Union kann einen Typ mehrfach besitzen. Wird eine Union Default initialisiert, erhält sie den Wert ihres ersten Typs. Daher muss in diesem Fall der erste Typ einen Default-Konstruktor besitzen. Hier ist ein Beispiel, basierend auf cppreferenc.com.
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 |
// variant.cpp #include <variant> #include <string> int main(){ std::variant<int, float> v, w; v = 12; // v contains int int i = std::get<int>(v); w = std::get<int>(v); w = std::get<0>(v); // same effect as the previous line w = v; // same effect as the previous line // std::get<double>(v); // error: no double in [int, float] // std::get<3>(v); // error: valid index values are 0 and 1 try{ std::get<float>(w); // w contains int, not float: will throw } catch (std::bad_variant_access&) {} std::variant<std::string> v("abc"); // converting constructors work when unambiguous v = "def"; // converting assignment also works when unambiguous } |
Ich definiere in Zeile 8 die beiden Varianten v und w. Beide besitzen einen int und einen float Wert. v wird zu 12 in Zeile 9. std::get<int>(v) gibt den Wert zurück. In den Zeilen 11-13 verwende ich drei Möglichkeiten um der Variante v den Wert von w zuzuweisen. Aber du musst ein paar Regeln im Kopf behalten. Du kannst nach dem Wert einer Variante mittels des Typs (Zeile 15) oder des Index (Zeile 16) abfragen. Der Typ muss eindeutig sein und der Index gültig. Die Variante w in Zeile 19 besitzt einen int Wert. Daher erhalte ich eine std::bad_variant_access Ausnahme in Zeile 21. Falls der Konstruktor Aufruf oder die Zuweisung eindeutig ist, findet eine Konvertierung statt. Das ist der Grund, dass ich in Zeile 23 einen std::variant<std::string> mit einem C-String initialisieren oder einen C-String in Zeile 24 zuweisen kann.
Wie geht`s weiter?
Ich bleibe beim C++17 Standard. Nachdem ich in diesem und dem letzten Artikel einen Überblick zur Kernsprache und zur Bibliothek gegeben habe, werden ich im nächsten Artikel tiefer in die Details eintauchen.
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...