namespace cpp

C++ lernen, kennen, anwenden

Benutzer-Werkzeuge

Webseiten-Werkzeuge


kennen:module

Module

Abschied vom Präprozessor?

Programme bestehen aus mehreren Übersetzungseinheiten, abgesehen von Ausnahmen:

#include <iostream>
int main()
{
    std::cout << "Hallo, Welt\n";
}

Das Einbinden anderer Quellen per #include verlangsamt den Übersetzungsprozess. Umfangreiche Header müssen immer wieder mit übersetzt werden. Manche Compiler behelfen sich mit Vorübersetzung (precompiled headers). Reihenfolge- und zyklische Abhängigkeiten (#ifdefund #pragma once), Verletzungen der „one definition rule“, ungenügende Abschirmung von Implementierungsdetails (Pimpl-Idiom, namespace xy::detail) und vor unbeabsichtigten Wirkungen von Makros (__Uglified_Names in Bibliotheken) sind negative Folgen dieser Technik. Module ändern ab C++20 die Organisation von Quelltexten, den Übersetzungsprozess und die Arbeit von Werkzeugen (Build-Systeme, Entwicklungsumgebungen) tiefgreifend. Der Umstellungsprozess wird aber Jahre dauern.

Module importieren

Übersetzungseinheiten können Module importieren und nutzen:

import hello;
int main()
{
    greeting();
}

Importierbar sind (übergangsweise?) auch C++-Header wie <iostream>. Die Standardbibliothek wurde in C++20 noch nicht modularisiert. Modulnamen, die mit std, evtl. gefolgt von Ziffern, beginnen, sind für die Standardbibliothek reserviert. Modulnamen können durch Punkte unterteilt werden: std.core und std.core.algorithm wären trotz gleicher Namensteile voneinander unabhängige Module.

Schnittstelle

Ein Modul besteht aus genau einer Modul-Schnittstellen-Datei und null bis vielen Implementierungsdateien. Kennzeichen einer Modulschnittstelle ist die Anweisung export module modulname;

export module hello;
export void greeting();

In der Schnittstelle werden alle zum Modul gehörenden und vom Modul exportierbaren Namen aufgeführt. Das Schlüsselwort export steht vor einzelnen Deklarationen, vor Klassen, Funktionen, Schablonen, Namensräumen, oder als Block export {...}. Makros werden nicht exportiert.

Implementierung

Funktionen werden in der Modulschnittstelle oder anderen zum Modul gehörenden Dateien definiert:

module hello;
import <iostream>;
void greeting() { std::cout < "Hello, world\n"; }

Die Zugehörigkeit zu einem Modul wird durch module modulname; festgelegt. Dabei kann auf alle in der Schnittstelle aufgeführten Namen zurückgegriffen werden. Modulname und import-Anweisungen stehen vor allem anderen Deklarationen eines Moduls.

Reexportieren

export module noodles;
export import spaghetti;
...

erlaubt, die Schnittstellen anderer Module mit zu exportieren.

Partitionen

export module geometry:vector;
...

Modulpartitionen, erkennbar am Doppelpunkt im Namen, können nur von dem übergeordneten Modul importiert und auch reexportiert werden:

export module geometry;
export import :vector;

Auch Modulpartitionen können neben der Schnittstelle weitere Implementierungsdateien besitzen.

Globales und privates Fragment

Eine Modul(schnittstellen)datei hat zwingend folgende Struktur (Teile in Eckigen Klammern können wegfallen):

[                           // nur Kommentare
module;                     // globales Modulfragment
...    ]                    // nur Präprozessoranweisungen
[export] modul modulname;   // Modulpräambel
[export][import andere;]    // Importbereich
...                         // Modulbereich
[module :private;           // privates Modulefragment 
...    ]

Eine Modulschnittstelle mit privatem Modulbereich darf keine weiteren Implementierungsdateien haben.

Modul-Bindung

Neben lokaler und externer Bindung führt das Modulsystem eine weitere Bindungsart ein. Innerhalb von Modulschnittstellen definierte Variablen sind innerhalb aller Übersetzungseinheiten des Moduls nutzbar. Mit anderen externen Variablen kollidieren sie nicht, wenn sie nicht exportiert werden, wie dieses Beispiel mit mehreren Modulpartitionen zeigt:

export module walk:position;
int x = 0;
export module walk:steps;
import :position;
 
export void forward() { ++x; }
export void back() { --x; }
export module walk;
export import :steps;
import :position;
 
export auto get_position() { return x; }
// does not collide with x in module walk:
int x = 7;
import walk;
import std.core; // Visual C++ 2019: instead of 
// import <iostream>; 
 
int main()
{
    forward();
    forward();
    back();
    std::cout << get_position() << " steps gone\n";
 
    extern int x;
    std::cout << x << " is another, unaffected global variable\n";
}

Übersetzungsprozess

Modulschnittstellen als auch Implementierungsdateien sind einzelne Übersetzungseinheiten, die beim Übersetzen zu Ohjektdateien (*.o, *.obj) werden. Bevor Module importiert werden können, müssen die Modulschnittstellen bekannt sein. Dazu müssen die Schnittstellen zuerst, in der Reihenfolge ihrer Abhängigkeiten, verarbeitet werden. Zyklische Abhängigkeiten sind nicht erlaubt. Die Art, wie Compiler die Informationen dieser Schnittstellen aufbereiten, vorhalten und wiederfinden, z.B. als binary module interface (*.bmi, *.ifc), ist implementierungsabhängig. Hier kommt noch viel Arbeit auf die Hersteller von Buildsystemen zu.

Übersetzungseinheit Dateiendung1)erzeugt
Modul-Schnittstelle export module name;*.cppm / *.ixx .o und *.bmi / *.ifc
Importierbare Header import <header>;
import “header”; *.hpp / *.h .o und *.bmi / *.ifc
Modul-Implementierungmodule name; *.cpp .o
Konsument import name; *.cpp .o
*.ixx -> Prepocess/Compile -> *.obj  Libraries           
            v                  \    / 
           *.ifc                Link -> program
            v                  /
*.cpp -> Compile -> *.obj
1)
Dateiendungen sind System- bzw. Compiler-implementierungsabhängig.
kennen/module.txt · Zuletzt geändert: 2020-07-29 17:32 von rrichter