Blog 4 Min. Lesezeit

Warum sind C++-Module für die Zukunft von C++ so wichtig?

Diesen Artikel teilen
Why is the C++ modules feature so important for the future of C++?

C++ stagnierte viele Jahre lang, und zahlreiche Entwickler waren überzeugt, dass die Sprache dasselbe Schicksal wie COBOL, Fortran und VB6 erleiden würde: Es würden keine neuen Projekte mehr damit entwickelt, und C++-Entwickler würden lediglich bestehende Projekte warten. Doch entgegen allen Erwartungen erhob sich C++ aus der Asche, und die neuen Standards veränderten grundlegend, wie die Sprache eingesetzt wird.

Der traditionelle #include -Mechanismus ist jedoch weiterhin vorhanden. Nach der Modernisierung der Sprache wurde er zum nächsten Schwachpunkt, der angegangen werden musste. Tatsächlich bringt er zahlreiche Nachteile mit sich; einige davon stammen aus diesem interessanten Dokument.

  • Skalierbarkeit der Kompilierzeit: Jedes Mal, wenn ein Header eingebunden wird, muss der Compiler den Text dieses Headers sowie transitiv alle von ihm eingebundenen Header vorverarbeiten und parsen. Dieser Vorgang muss für jede Übersetzungseinheit der Anwendung wiederholt werden, wodurch enorm viel redundante Arbeit entsteht. In einem Projekt mit N Übersetzungseinheiten und M Headern pro Übersetzungseinheit führt der Compiler M x N Arbeitsschritte aus, obwohl die meisten der M Header von mehreren Übersetzungseinheiten gemeinsam genutzt werden. C++ ist davon besonders betroffen, weil das Kompilierungsmodell für Templates große Mengen an Code in Header-Dateien zwingt.
  • Fragilität: #include -Direktiven werden vom Präprozessor als textuelle Einbindung behandelt und unterliegen daher allen zum Zeitpunkt der Einbindung aktiven Makrodefinitionen. Kollidiert eine aktive Makrodefinition zufällig mit einem Namen in der Bibliothek, kann dies die Bibliotheks-API beschädigen oder Kompilierungsfehler im Bibliotheks-Header selbst verursachen. Ein extremes Beispiel: #define std "The C++ Standard" und anschließend einen Header der Standardbibliothek einbinden: Das Ergebnis ist eine verheerende Kaskade von Fehlern in der Implementierung der C++-Standardbibliothek. Subtilere Probleme aus der Praxis entstehen, wenn Header zweier verschiedener Bibliotheken aufgrund von Makrokollisionen miteinander interagieren und Benutzer gezwungen sind, #include -Direktiven neu anzuordnen oder #undef -Direktiven einzuführen, um die unbeabsichtigte Abhängigkeit aufzubrechen.
  • Konventionelle Workarounds: C-Programmierer haben verschiedene Konventionen entwickelt, um die Fragilität des C-Präprozessormodells zu umgehen. Include Guards sind beispielsweise für die überwiegende Mehrheit der Header erforderlich, damit mehrfaches Einbinden die Kompilierung nicht beeinträchtigt. Makronamen werden mit LONG_PREFIXED_UPPERCASE_IDENTIFIERS geschrieben, um Kollisionen zu vermeiden, und manche Bibliotheks- oder Framework-Entwickler verwenden sogar __underscored -Namen in Headern, um Kollisionen mit „normalen“ Namen zu vermeiden, die gemäß Konvention eigentlich gar keine Makros sein sollten. Diese Konventionen erschweren Entwicklern aus Nicht-C-Sprachen den Einstieg, sind für erfahrene Entwickler unnötiger Boilerplate und machen Header deutlich unübersichtlicher, als sie sein müssten.
  • Probleme für Entwicklungswerkzeuge: In einer C-basierten Sprache ist es schwierig, Werkzeuge zu entwickeln, die zuverlässig mit Softwarebibliotheken arbeiten, weil deren Grenzen nicht eindeutig sind. Welche Header gehören zu einer bestimmten Bibliothek, und in welcher Reihenfolge müssen sie eingebunden werden, damit sie korrekt kompilieren? Handelt es sich um C-, C++-, Objective-C++-Header oder Varianten dieser Sprachen? Welche Deklarationen in diesen Headern sind tatsächlich Teil der API, und welche sind nur vorhanden, weil sie in der Header-Datei stehen mussten?

Eine Lösung für diese Probleme wurde bereits in den ersten Entwürfen von C++0x : das Modules-Feature. Mit jeder neuen Standardspezifikation wurde es jedoch erneut verschoben.

Module: ein robusteres semantisches Modell

Module verbessern den Zugriff auf Bibliotheken durch ein robusteres und effizienteres semantisches Modell. Aus Sicht des Benutzers sieht der Code nur geringfügig anders aus, da eine import -Deklaration anstelle einer #include -Präprozessordirektive verwendet wird. Hier ein Beispiel aus dem C++0x-Entwurf:

import std; // Module import directive.
int main() {
    std::cout << "Hello World\n";
}

Es ist nicht mehr erforderlich, mehrere STL-Dateien einzubinden – ein einziger Import genügt, wodurch der Code übersichtlicher wird. Der Modulimport lädt eine binäre Repräsentation des std -Moduls und stellt dessen API der Anwendung direkt zur Verfügung. Präprozessordefinitionen vor der Import-Deklaration haben keinen Einfluss auf die von stdbereitgestellte API, da das Modul selbst separat als eigenständiges Modul kompiliert wurde.

Hier ist ein interessanter Beitrag, der zeigt, wie sich ein C++-Modul mit VS2017 entwickeln lässt: C++-Module in Visual Studio 2017.

Fazit

C++-Module beseitigen die mit dem Include-Mechanismus verbundenen Probleme und vereinfachen die Verwendung anderer Bibliotheken erheblich – ähnlich wie in C# oder Java.

Erstellen Sie Ihr erstes Modul und entdecken Sie die Möglichkeiten dieses leistungsstarken Features.

Diesen Artikel teilen