C++20: Ăśberblick zur Bibliothek
Nach der Vorstellung der vier groĂźen Features von C++20 und dem Ăśberblick zur Kernsprache folgt nun der zur Bibliothek.
- Rainer Grimm
Mein letzter Artikel "C++20: Ăśberblick zur Kernsprache" stellte kurz und kompakt die neuen Features der C++20-Kernsprache vor. Heute geht meine Geschichte zu C++20 mit dem Ăśberblick der Bibliothek weiter.
Wie gewohnt zeigt die Grafik, um welche Feature sich der heutige Artikel dreht:
Kalender und Zeitzonen
Die Chrono-Bibliothek von C++11/14 wurde um einen Kalender und Zeitzonen erweitert.
- Kalender: Er besteht aus Datentypen, die ein Jahr, einen Monat, ein Wochentag und den n-ten Tag der Woche repräsentieren. Diese elementaren Datentypen können zu komplexen Datentypen wie
year_month
,year_month_day
,year_month_day_last
,years_month_weekday
undyear_month_weekday_last
verknüpft werden. Der "/" Operator erlaubt den einfache Umgang mit Zeitpunkten. Zusätzlich gib es noch zwei neue Literale in C++20:d
fĂĽr einen Tag undy
fĂĽr ein Jahr.
- Zeitzonen: Zeitpunkte lassen sich in verschiedenen Zeitzonen darstellen.
Dank der erweiterten Chrono-Bibliothek, lassen sich die folgenden Anwendungen umsetzen:
- ein Datum in verschiedenen Formaten repräsentieren:
auto d1 = 2019y/oct/28;
auto d2 = 28d/oct/2019;
auto d3 = oct/28/2019;
- bestimme den letzten Tag eines Monats
- bestimme die Anzahl der Tage zwischen zwei Daten
- die Ausgabe der aktuellen Zeit in verschiedenen Zeitzonen
Um erste Erfahrung mit der erweiterten Bibliothek zu sammeln, bietet sich Howard Hinnards Implementierung auf GitHub an. Er ist der Autor des Proposals fĂĽr die Kalender- und Zeitzonenerweiterung. Auf Wandbox hat er eine Spielwiese fĂĽr die neuen Features erzeugt:
#include "date.h"
#include <iostream>
int
main()
{
using namespace date;
using namespace std::chrono;
auto now = system_clock::now();
std::cout << "The current time is " << now << " UTC\n";
auto current_year = year_month_day{floor<days>(now)}.year();
std::cout << "The current year is " << current_year << '\n';
auto h = floor<hours>(now) - sys_days{jan/1/current_year};
std::cout << "It has been " << h << " since New Years!\n";
}
Es ist offensichtlich, C++20 wird den Namensraum std::chrono
anstelle des Namensraums date
verwenden. Hier ist die Ausgabe des Programms:
std::span
std::span
steht für ein Objekt, das sich auf einen zusammenhängende Sequenz von Objekten bezieht. Ein std::span
, manchmal auch View genannt, ist niemals ein Besitzer. Der zusammenhängende Speicherbereich kann ein Array, ein Zeiger mit einer Länge oder ein std::vector
sein. Eine typische Implementierung eines std::span
benötigt einen Zeiger auf das erste Element der Sequenz und seine Länge. Der wichtigste Grund, dass std::span
im C++20 Standards enthalten ist, ist der Tatsache geschuldet, dass ein C-Array zu einem Zeiger vereinfacht wird (decay), wenn dieses an eine Funktion ĂĽbergeben wird. Daher bestimmt std::span<T>
automatisch die Länge eines einfaches C-Arrays oder eines std::vector
. Um einen Zeiger zum Initialisieren eines std::span<T>
zu verwenden, benötigt der Konstruktor seine Länge:
template <typename T>
void copy_n(const T* p, T* q, int n){}
template <typename T>
void copy(std::span<const T> src, std::span<T> des){}
int main(){
int arr1[] = {1, 2, 3};
int arr2[] = {3, 4, 5};
copy_n(arr1, arr2, 3); // (1)
copy(arr1, arr2); // (2)
}
Im Unterschied zu der Funktion copy_n (1)
benötigt die Funktion copy (2)
nicht die Anzahl der Elemente. Daher wird eine groĂźe Fehlerquelle mit std::span<T>
beseitigt.
constexpr-Container
Das constexpr
-SchlĂĽsselwort wird immer dominanter in C++. Zum Beispiel werden viele Algorithmen der Standard Template Library eine neue Ăśberladung mit constexpr
erhalten. Es steht für eine Funktion oder ein Funktions-Template und bedeutet, dass die Funktion oder das Funktions-Template potenziell zur Compilezeit ausgeführt werden können. Natürlich stellt sich jetzt die Frage, welche Container zur Compilezeit verwendet werden können? Mit C++20 heißt die Antwort std::string
und std::vector
.
Vor C++20 konnten beide Container nicht in einem Kontext verwendet werden, der constexpr
-Container voraussetzt, da beide drei Einschränkungen besaßen:
- Destruktoren konnten nicht
constexpr
sein. - Dynamische Memory-Allokation war zur Compilezeit nicht möglich.
- Das Erzeugen von Objekten an Ort und Stelle mit
placement-new
war nicht möglich.
Diese Einschränkungen sind mit C++20 verschwunden.
Der Punkt 3 bezieht sich auf das relativ unbekannte placement-new
. Es wird gerne verwendet, um ein Objekt in einem vorreservierten Speicherbereich zu instanziieren. DarĂĽber hinaus kann placement-new
global und fĂĽr eigene Datentypen ĂĽberladen werden.
char* memory = new char[sizeof(Account)]; // allocate memory
Account* account = new(memory) Account; // construct in-place
account->~Account(); // destruct
delete[] memory; // free memory
Diese Schritte sind notwendig fĂĽr placement-new
. In der ersten Zeile wird der Speicher fĂĽr Account angefordert, der in der folgenden Zeile direkt verwendet wird. Zugegeben, der Ausdruck account->~Account
schaut befremdlich aus. Dieser Aufruf stellt eine der Ausnahmen dar, in der der Anwender den Konstruktor direkt aufrufen muss. Zum Abschluss gibt die letzte Zeile den Speicher wieder frei.
Ich werde nicht tiefer in die Details zu constexpr
-Container abtauchen. Falls du mehr Details lesen willst, hier ist das Proposal 784R1.
std::format
cppreference.com beschreibt kurz und bĂĽndig die neue Bibliothek std::format
: "The text formatting library offers a safe and extensible alternative to the printf family of functions. It is intended to complement the existing C++ I/O streams library and reuse some of its infrastructure such as overloaded insertion operators for user-defined types." Diese Beschreibung umfasst ein einfaches Beispiel:
std::string message = std::format("The answer is {}.", 42);
Diese Syntax erinnert mich sehr an die Formatstrings von Python. Es gibt bereits eine Implementierung von std::format
auf Github: fmt. Gerne möchte ich noch ein paar Beispiele aus der erwähnten Implementierung vorstellen. In den Beispiel kommt der Namensraum fmt
anstelle des Namensraums std
zum Einsatz.
- Formatiere und verwende Positionsargumente:
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
// s == "I'd rather be happy than right."
- Konvertiere eine Ganzzahl auf sichere Weise in einen String:
fmt::memory_buffer buf;
format_to(buf, "{}", 42); // replaces itoa(42, buffer, 10)
format_to(buf, "{:x}", 42); // replaces itoa(42, buffer, 16)
// access the string with to_string(buf) or buf.data()
- Formatiere benutzerdefinierte Datentypen:
struct date {
int year, month, day;
};
template <>
struct fmt::formatter<date> {
template <typename ParseContext>
constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const date &d, FormatContext &ctx) {
return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
}
};
std::string s = fmt::format("The date is {}", date{2012, 12, 9});
// s == "The date is 2012-12-9"
Wie geht's weiter?
Wie versprochen, werde ich mich in weiteren Artikeln genauer mit der C++20-Bibliothek beschäftigen. Zuerst möchte ich aber meinen Überblick zu C++20 abschließen. Der nächste Artikel setzt sich mit den neuen Featuren rund um Concurrency auseinander.