C++17: Neuzugänge in den Bibliotheken

Seit Juni ist C++17 Feature-komplett. Das ist ein guter Anlass, die Neuerungen zu betrachten. Nachdem es im vorherigen Artikel um einige generelle Aspekte und die Sprachmittel ging, stehen nun die neuen Bibliotheksfunktionen im Mittelpunkt.

Sprachen  –  6 Kommentare
C++17: Neuzugänge in den Bibliotheken

Viele der in C++17 aufgenommenen Bibliotheken sind bereits in Boost vorhanden und weisen damit eine zum Teil große Anwendungserfahrung auf. Im Rahmen der Aufnahme in C++17 kann sich dennoch etwas ändern – vor allem, weil die Macher alle bis C++17 hinzugekommenen Sprachmittel voraussetzen können.

Portabler Zugriff auf das Dateisystem

Die größte von Boost adaptierte Bibliothek ist "Filesystem" für den bequemen und portablen Zugriff auf Dateisysteme. Das folgende Beispiel zeigt die Ausgabe der Größe einer Datei beziehungsweise, falls filename für ein Verzeichnis steht, das Auflisten dessen Inhalts:


std::filesystem::path p(filename);
if (exists(p)) {
if (is_regular_file(p)) {
std::cout << " size of " << p
<< " is " << file_size(p) << '\n';
}
else if (is_directory(p)) {
// liste Directory auf:
std::cout << p << " is a directory containing:\n";
for (auto& e : std::filesystem::directory_iterator(p)) {
std::cout << " " << f.path() << '\n';
}
}
}

Bei der Adaption gab es einige Verbesserungen. Das Listing zeigt, dass neuerdings das Iterieren über die Dateien eines Verzeichnisses bequem mit einer Range-based-for-Schleife möglich ist. Mit solch einer Schleife kann man übrigens auch rekursiv durch Verzeichnisbäume gehen. Weitere Neuerungen sind Befehle zum Anlegen von Elementen wie Dateien, Verzeichnissen und symbolische Links.

Im Rahmen der Aufnahme in C++17 haben die Macher außerdem die lange gewünschte Funktion zur Berechnung relativer Pfade aus zwei absoluten hinzugefügt. Herausgekommen ist ein ganzes Sammelsurium von Funktionen, da Entwickler manchmal das Dateisystem berücksichtigen wollen, um etwa Symbolic-Links korrekt zu behandeln, an anderen Stellen jedoch unabhängig davon arbeiten möchten. An der Stelle zeigt sich, dass eine solche Bibliothek Eigenschaften unterschiedlicher Dateisysteme nur bedingt kapseln kann, da diese jeweils große Eigenheiten aufweisen können. Unter dem Aspekt ist die Bibliothek bemerkenswert portabel.

Strings ohne Speicherplatz

Mit der Klasse string_view hat eine weitere String-Klasse Einzug in den Standard gehalten. Sie enthält den Speicherplatz der Zeichenfolge nicht selbst, sondern repräsentiert einen Verweis auf eine externe. Das beschleunigt das Kopieren, da nur der Verweis statt des Inhalts repliziert wird. Der Anwender muss jedoch darauf achten, dass der Speicherplatz zur Verfügung steht, solange ein string_view existiert.

Anders als bisherige String-Klassen (oben) enthält string_view (unten) nur ein Verweis auf eine Zeichenfolge (Abb. 1).

Die Nutzung der Klasse ist beispielsweise besonders hilfreich, wenn das Programm mit mmap() Daten aus Dateien direkt in den Hauptspeicher gemappt hat und die Daten ohne zeitintensives Verwalten des Heap-Speicherplatzes verwenden soll.

Funktionen, die Strings als Parameter verwenden, können (und sollten) nun zur Unterstützung statt const string& den Datentyp string_view verwenden, wenn folgende Umstände gegeben sind:

  1. Es wird kein Null-Terminator am Ende benötigt (der String also z.B. nicht an eine C-Funktion durchgereicht).
  2. Die Lebenszeit des Strings wird nicht verlängert, ohne intern eine Kopie zu ziehen.

Wenn ein Aufrufer solcher Funktionen zum Beispiel eine String-Konstante übergibt, muss für den string_views-Parameter lediglich die Länge des Strings ermittelt werden, es entfällt das Erzeugen eines temporären Objekts vom Typ string.

Neue Arten von Datenstrukturen

In C++17 sind drei neue Arten von generischen Datenstrukturen hinzugekommen, die die Sprachmittel sinnvoll ergänzen: std::optional, std::any und std::variant. Auch wenn boost.org Vorläufer zu den Strukturen enthält, gab es teilweise große Änderungen – insbesondere bei std::variant.

Der Datentyp std::optional dient dazu, existierende Datentypen mit der zusätzlichen Fähigkeit zu versehen, den Wert "kein Wert gesetzt" vergeben und abfragen zu können – somit bekommt die Wertesemantik zusätzlich den Wert NULL beziehungsweise NIL. Die Objekte können Auskunft geben, ob ein Wert gesetzt ist. Ist das der Fall, können Entwickler ihn direkt verwenden.

Wenn beispielsweise eine Funktion optional einen String zurückliefert, der auch leer sein kann

std::optional<std::string> foo();

kann sie einen String wie folgt zurückliefern:

return std::string(...);

Das darf auch der Leerstring sein:

return "";

Die Funktion kann aber auch keinen(!) String zurückliefern, wodurch sich eine andere Semantik als bei einem zurückgelieferten Leerstring ergibt:

return std::optional<std::string>();

Oder in "moderner" C++-Schreibweise:

return std::optional<std::string>{};

Der Rückgabewert lässt sich dann wie folgt auswerten:

const std::optional<std::string>& s = foo();
if (s) {
std::cout << "foo() returned '" << *s << "'\n";
}

Der Zugriff erfolgt also mit *s. Der vorherige Test ist notwendig, da das Verhalten undefiniert ist, wenn kein Wert gesetzt ist. Der alternative Zugriff mit s.value() wirft in dem Fall eine Exception.