iX 8/2019
S. 46
Titel
Programmierung

Die Neuerungen in C++20

Die großen Fünf

Rainer Grimm

Mit C++20 steht ein revolutionäres Update der Sprache vor der Tür. Concepts, die Ranges-Bibliothek, Module und Coroutinen werden grundlegend neu definieren, wie man modernes C++ programmiert. Die ebenfalls für C++20 geplanten Contracts wurden kurzfristig verschoben.

Der aktuelle C++17-Standard konzentrierte sich vor allem auf zwei Features: die Dateisystembibliothek und die parallelisier- und vektorisierbaren Algorithmen der Standard Temp­late Library. Der kommende Standard C++20 dagegen bildet die nächste Evolutions­stufe der Sprache selbst. Dabei geht es um fünf neue Sprachfeatures:

Concepts sind eine Erweiterung von Templates, mit denen Entwickler semantische Kategorien für die Menge der zulässigen Datentypen festlegen. Mit ihrer Hilfe lassen sich Templates deutlich einfacher und ausdrucksreicher nutzen.

Nachdem C++14 und C++17 nur vergleichsweise kleine Änderungen an der Sprache brachten, steht C++20 mit vielen grundlegenden Neuerungen in der Tradition der „großen“ Standards C++98 und C++11 (Abb. 1).

Die Ranges-Bibliothek erlaubt es, die Algorithmen der Standard Template Library direkt auf Container anzuwenden, sie mit dem aus der Unix-Shell bekannten Pipe-­Operator zu verknüpfen und auf unend­lichen Datenströmen zu definieren.

Module sind eine Alternative zu Header-­Dateien und versprechen viele Verbesserungen. Dazu gehört, die Trennung von Header- und Sourcecodedateien aufzu­lösen, Präprozessoranweisungen zu eliminieren, das Kompilieren zu beschleunigen und einfacher Pakete schnüren zu können.

Mit Coroutinen öffnet sich C++20 für die asynchrone Programmierung. Damit werden sich in der neuen Version ko­operatives Multitasking, unendliche Daten­ströme, Event-Schleifen und Pipelines elegant umsetzen lassen. 

Contracts definieren Interfaces für Funktionen. Diese Interfaces können aus Vorbedingungen für die Funktionsargumente, Nachbedingungen für das Ergebnis einer Funktion und Zusicherungen während der Ausführung der Funktion bestehen.

Die fünf vorgestellten Features finden sich im C++20-Entwurf. Allerdings wird es wahrscheinlich noch Anpassungen an der konkreten Syntax geben.

Contracts für bessere Interfaces

Hinter einem Contract steckt in einer ­präzisen und prüfbaren Art ein Interface für eine Softwarekomponente (Abbildung 2). Damit lässt sich das Konzept „Design by contract“ in C++ umsetzen.

Design by contract: Definierte Vor- und Nachbedingungen sollen das Zusammenspiel von Programm-Modulen verbessern (Abb. 2).

Die Softwarekomponente ist typischerweise eine Funktion oder eine Methode, die Vorbedingungen, Nachbedingungen und Invarianten erfüllen muss. Hier sind die Definitionen kurz und kompakt:

  • Eine Vorbedingung (Precondition) ist ein Prädikat – eine Funktion, die einen Wahrheitswert zurückgibt –, das erfüllt sein muss, bevor die Komponente aufgerufen wird.
  • Eine Nachbedingung (Postcondition) ist ein Prädikat, das gelten muss, nachdem die Komponente aufgerufen wurde.
  • Eine Zusicherung (Invariante) ist ein Prädikat, das an der Stelle im Code gelten muss, an der es platziert ist.

Standardmäßig führt eine Verletzung eines Kontrakts zur Beendigung des Programms. Vor- und Nachbedingungen müssen Entwickler in C++20 außerhalb der Funktionsdefinition platzieren, Zusicherungen innerhalb der Funktionsdefinition. Listing 1 zeigt ein Beispiel.

Beim Attribut expects handelt es sich um eine Vorbedingung, beim Attribut ensures um eine Nachbedingung und beim Attribut assert um eine Zusicherung. Die Contracts für die Funktion push besagen, dass die Queue nicht voll sein darf, bevor ein Element hinzukommt, dass sie nicht leer ist, nachdem ein Element hinzugefügt wurde, und dass die Zusicherung q.is_ok() gilt.

Vor- und Nachbedingungen gehören zum Funktionsinterface und können damit nicht auf lokale Variablen und private oder protected Mitglieder einer Klasse zugreifen. Für Zusicherungen gilt diese Einschränkung nicht, da sie Teil der Implementierung sind.

Mehr Syntax

Der folgende Ausdruck veranschaulicht die vollständige Syntax für Contracts: 

[[contract-attribute modifier: conditional-expression ]]

modifier steht für die Abstufung des Contracts, also für seine Durchsetzung. Mögliche Werte sind defaultaudit und axiom. Hinter conditional-expression verbirgt sich das Prädikat des Kontrakts.

Nachbedingungen (ensures-Attribut) können über einen Identifier den Rück­gabewert der Funktion wie in Listing 2 prüfen. res ist hier ein beliebiger Name. Wie das Beispiel zudem zeigt, lassen sich mehrere Contracts derselben Art ­definieren.

Beim Kompilieren lässt sich einstellen, wie die Contracts zur Laufzeit geprüft werden sollen:

  • off: keine Prüfung;
  • default: Prüfung von Contracts mit dem Modifier default (Standardverhalten);
  • audit: Prüfung von Contracts mit den Modifiern default und audit

Wenn der Vertrag verletzt wird 

Bei einer Verletzung eines Kontrakts, also wenn das Prädikat zu false evaluiert, wird ein Violation Handler ausgerufen. Er ist eine Funktion vom Typ noexcept, die ein Argument

const std::contract_violation

annimmt und void zurück gibt. Da die Funktion als noexcept deklariert ist, wird im Fall einer Verletzung des Kontrakts die Funktion std::terminate aufgerufen. Entwickler können hier einen eigenen Vio­lation Handler einsetzen. Die Klasse std::contract_violation in Listing 3 liefert Informationen bei der Verletzung eines Kontrakts.

Welchen Einfluss Contracts auf die Entwicklung in C++ haben werden, lässt sich nur erahnen. Herb Sutter, Vorsitzender der Standardisierungskomitees, prophezeit ihnen einen sehr großen Einfluss: „Contracts is the most impactful feature of C++20 so far, and arguably the most impactful feature we have added to C++ since C++11“ (siehe ix.de/zvpy).

Concepts präzisieren Templates

Concepts sind ein sehr mächtiges und umfangreiches Feature, daher beschränkt sich dieser Artikel auf ihre wichtigsten Eigenschaften. Wer tiefer abtauchen will, wird in dem Titelthema der iX 12/2017 und einigen englischen Blogartikeln des Autors fündig (siehe ix.de/zvpy). Concepts gehören zur generischen Programmierung in C++ und bringen verschiedene Vorteile. 

Über Concepts lassen sich Anforderungen an Templates als Teil des Interface formulieren, die der Compiler verifizieren kann. Sie legen semantische Kategorien für die zulässigen Datentypen wie „lässt sich auf Gleichheit prüfen“ oder „ist sortierbar“ fest. Das verhindert bereits beim Übersetzen, dass ein Template mit einem ungeeigneten Datentyp instanziiert wird, und macht es möglich, Funktionen zu überladen und Klassen-Templates zu spezialisieren. Außerdem erzeugt der Compiler deutlich verbesserte Fehlermeldungen, da er Anforderungen an die Template-Parameter mit den aktuellen Template-Argumenten prüft. Weiterhin lassen sich eigene Concepts definieren und verfeinern. 

Daneben wird die Syntax der automatischen Typableitung mit auto und Concepts vereinheitlicht. Konkret heißt das: Anstelle des Schlüsselwortes auto lässt sich direkt ein Concept wie Gleichheit verwenden. Damit ist ein vom Compiler automatisch abgeleiteter Datentyp nur dann zulässig, wenn er das Concept Gleichheit erlaubt.

Concepts für Funktionen und Klassen

Die Grundfunktion von Concepts ist schnell erklärt. Concepts sind Teil der Templatedeklaration. Das Funktions-Template sort fordert, dass der Container sortierbar (Sortable) sein muss:

template<Sortable Cont>
void sort(Cont& container){...}

Äquivalent lässt sich die Anforderung an den Template-Parameter explizit formulieren:

Kommentare lesen (1 Beitrag)