Modules (since C++20)
Die meisten C++-Projekte verwenden mehrere Übersetzungseinheiten und müssen daher Deklarationen und Definitionen über diese Einheiten hinweg teilen. Die Verwendung von Headern ist für diesen Zweck weit verbreitet, ein Beispiel ist die Standardbibliothek , deren Deklarationen durch Einbinden der entsprechenden Header bereitgestellt werden können.
Module sind ein Sprachmerkmal, um Deklarationen und Definitionen über Übersetzungseinheiten hinweg zu teilen. Sie sind eine Alternative für einige Anwendungsfälle von Headern.
Module sind orthogonal zu namespaces .
// helloworld.cpp export module helloworld; // Moduldeklaration import <iostream>; // Importdeklaration export void hello() // Exportdeklaration { std::cout << "Hello world!\n"; }
// main.cpp import helloworld; // Import-Deklaration int main() { hello(); }
Inhaltsverzeichnis |
Syntax
export
(optional)
module
module-name module-partition
(optional)
attr
(optional)
;
|
(1) | ||||||||
export
declaration
|
(2) | ||||||||
export {
declaration-seq
(optional)
}
|
(3) | ||||||||
export
(optional)
import
module-name attr
(optional)
;
|
(4) | ||||||||
export
(optional)
import
module-partition attr
(optional)
;
|
(5) | ||||||||
export
(optional)
import
header-name attr
(optional)
;
|
(6) | ||||||||
module;
|
(7) | ||||||||
module : private;
|
(8) | ||||||||
Moduldeklarationen
Eine Übersetzungseinheit kann eine Moduldeklaration haben, in diesem Fall wird sie als eine Moduleinheit betrachtet. Die Moduldeklaration , falls vorhanden, muss die erste Deklaration der Übersetzungseinheit sein (mit Ausnahme des globalen Modulfragments , das später behandelt wird). Jede Moduleinheit ist einem Modulnamen (und optional einer Partition) zugeordnet, der in der Moduldeklaration angegeben wird.
export
(optional)
module
Modulname Modulpartition
(optional)
attr
(optional)
;
|
|||||||||
Der Modulname besteht aus einem oder mehreren durch Punkte getrennten Bezeichnern (zum Beispiel:
mymodule
,
mymodule.mysubmodule
,
mymodule2
...). Punkte haben keine inhärente Bedeutung, sie werden jedoch informell zur Darstellung von Hierarchien verwendet.
Wenn ein beliebiger Bezeichner im Modulnamen oder in der Modulpartition als ein objektartiges Makro definiert ist, ist das Programm fehlerhaft.
Ein benanntes Modul ist die Sammlung von Modul-Einheiten mit demselben Modulnamen.
Modul-Einheiten, deren Deklaration das Schlüsselwort export enthält, werden als Modul-Schnittstelleneinheiten bezeichnet; alle anderen Modul-Einheiten werden als Modul-Implementierungseinheiten bezeichnet.
Für jedes benannte Modul muss es genau eine Modulschnittstelleneinheit geben, die keine Modulpartition angibt; diese Moduleinheit wird als primäre Modulschnittstelleneinheit bezeichnet. Ihr exportierter Inhalt wird beim Import des entsprechenden benannten Moduls verfügbar sein.
// (jede Zeile stellt eine separate Übersetzungseinheit dar) export module A; // deklariert die primäre Modulschnittstelleneinheit für das benannte Modul 'A' module A; // deklariert eine Modulimplementierungseinheit für das benannte Modul 'A' module A; // deklariert eine weitere Modulimplementierungseinheit für das benannte Modul 'A' export module A.B; // deklariert die primäre Modulschnittstelleneinheit für das benannte Modul 'A.B' module A.B; // deklariert eine Modulimplementierungseinheit für das benannte Modul 'A.B'
Exportieren von Deklarationen und Definitionen
Modulschnittstelleneinheiten können Deklarationen (einschließlich Definitionen) exportieren, die von anderen Übersetzungseinheiten importiert werden können. Um eine Deklaration zu exportieren, setzen Sie entweder das export Schlüsselwort davor oder platzieren Sie sie in einem export Block.
export
Deklaration
|
|||||||||
export {
Deklarationssequenz
(optional)
}
|
|||||||||
export module A; // deklariert die primäre Modulschnittstelleneinheit für das benannte Modul 'A' // hello() wird für Übersetzungseinheiten sichtbar sein, die 'A' importieren export char const* hello() { return "hello"; } // world() wird NICHT sichtbar sein. char const* world() { return "world"; } // Sowohl one() als auch zero() werden sichtbar sein. export { int one() { return 1; } int zero() { return 0; } } // Das Exportieren von Namensräumen funktioniert ebenfalls: hi::english() und hi::french() werden sichtbar sein. export namespace hi { char const* english() { return "Hi!"; } char const* french() { return "Salut!"; } }
Importieren von Modulen und Header-Einheiten
Module werden über eine import declaration importiert:
export
(optional)
import
Modulname attr
(optional)
;
|
|||||||||
Alle Deklarationen und Definitionen, die in den Modulschnittstelleneinheiten des angegebenen benannten Moduls exportiert werden, sind in der Übersetzungseinheit verfügbar, die die Importdeklaration verwendet.
Importdeklarationen können in einer Modulschnittstelleneinheit exportiert werden. Das bedeutet, wenn Modul
B
Modul
A
export-importiert, dann wird durch das Importieren von
B
ebenfalls der gesamte Export aus
A
sichtbar.
In Modul-Einheiten müssen alle Import-Deklarationen (einschließlich Export-Importe) nach der Modul-Deklaration gruppiert werden und vor allen anderen Deklarationen stehen.
/////// A.cpp (primäre Modulschnittstelleneinheit von 'A') export module A; export char const* hello() { return "hello"; } /////// B.cpp (primäre Modulschnittstelleneinheit von 'B') export module B; export import A; export char const* world() { return "world"; } /////// main.cpp (keine Moduleinheit) #include <iostream> import B; int main() { std::cout << hello() << ' ' << world() << '\n'; }
#include sollte nicht in einer Moduleinheit (außerhalb des global module fragment ) verwendet werden, da alle eingebundenen Deklarationen und Definitionen als Teil des Moduls betrachtet würden. Stattdessen können Header auch als header units mit einer import declaration importiert werden:
export
(optional)
import
header-name attr
(optional)
;
|
|||||||||
Ein Header-Unit ist eine separate Übersetzungseinheit, die aus einem Header synthetisiert wird. Das Importieren einer Header-Unit macht alle ihre Definitionen und Deklarationen zugänglich. Präprozessor-Makros sind ebenfalls zugänglich (da Import-Deklarationen vom Präprozessor erkannt werden).
Allerdings werden, im Gegensatz zu #include , Präprozessormakros, die bereits zum Zeitpunkt der Import-Deklaration definiert sind, die Verarbeitung des Headers nicht beeinflussen. Dies kann in manchen Fällen unpraktisch sein (einige Header verwenden Präprozessormakros als eine Form der Konfiguration), in welchem Fall die Verwendung des global module fragment erforderlich ist.
/////// A.cpp (primäre Modulschnittstelleneinheit von 'A') export module A; import <iostream>; export import <string_view>; export void print(std::string_view message) { std::cout << message << std::endl; } /////// main.cpp (keine Moduleinheit) import A; int main() { std::string_view message = "Hello, world!"; print(message); }
` Tags (C++ Code) - C++ spezifische Begriffe (module, import, export, void, int, etc.) - String-Literal "Hello, world!"
Globaler Modulfragment
Modul-Einheiten können von einem globalen Modulfragment eingeleitet werden, welches verwendet werden kann, um Header einzubinden, wenn das Importieren der Header nicht möglich ist (insbesondere wenn der Header Präprozessor-Makros als Konfiguration verwendet).
module;
Präprozessordirektiven (optional) Moduldeklaration |
|||||||||
Wenn eine Modul-Einheit einen globalen Modulfragment hat, dann muss ihre erste Deklaration
module;
sein. Danach dürfen nur
Präprozessor-Direktiven
im globalen Modulfragment erscheinen. Danach markiert eine Standard-Moduldeklaration das Ende des globalen Modulfragments und den Beginn des Modulinhalts.
/////// A.cpp (primäre Modulschnittstelleneinheit von 'A') module; // Die Definition von _POSIX_C_SOURCE fügt Funktionen zu Standard-Headern hinzu, // gemäß dem POSIX-Standard. #define _POSIX_C_SOURCE 200809L #include <stdlib.h> export module A; import <ctime>; // Nur zur Demonstration (schlechte Quelle für Zufallszahlen). // Verwenden Sie stattdessen C++ <random>. export double weak_random() { std::timespec ts; std::timespec_get(&ts, TIME_UTC); // aus <ctime> // Bereitgestellt in <stdlib.h> gemäß dem POSIX-Standard. srand48(ts.tv_nsec); // drand48() gibt eine Zufallszahl zwischen 0 und 1 zurück. return drand48(); } /////// main.cpp (keine Moduleinheit) import <iostream>; import A; int main() { std::cout << "Zufälliger Wert zwischen 0 und 1: " << weak_random() << '\n'; }
Privates Modulfragment
Die primäre Modulschnittstelleneinheit kann durch einen private module fragment ergänzt werden, was es ermöglicht, ein Modul als einzelne Übersetzungseinheit darzustellen, ohne alle Inhalte des Moduls für Importierer erreichbar zu machen.
module : private;
Deklarationssequenz (optional) |
|||||||||
Private-Modulfragment beendet den Teil der Modulschnittstelleneinheit, der das Verhalten anderer Übersetzungseinheiten beeinflussen kann. Wenn eine Moduleinheit ein Private-Modulfragment enthält, wird sie die einzige Moduleinheit ihres Moduls sein.
export module foo; export int f(); module : private; // beendet den Teil der Modulschnittstelleneinheit, der // das Verhalten anderer Übersetzungseinheiten beeinflussen kann // startet einen privaten Modulfragment int f() // Definition für Importierer von foo nicht erreichbar { return 42; }
Modulpartitionen
Ein Modul kann
Modulpartitions-Einheiten
haben. Dies sind Moduleinheiten, deren Moduldeklarationen eine Modulpartition enthalten, die mit einem Doppelpunkt
:
beginnt und nach dem Modulnamen platziert wird.
export module A:B; // Deklariert eine Modulschnittstelleneinheit für Modul 'A', Partition ':B'.
Eine Modulpartition repräsentiert genau eine Moduleinheit (zwei Moduleinheiten können nicht dieselbe Modulpartition bezeichnen). Sie sind nur von innerhalb des benannten Moduls sichtbar (Übersetzungseinheiten außerhalb des benannten Moduls können eine Modulpartition nicht direkt importieren).
Eine Modulpartition kann von Moduleinheiten desselben benannten Moduls importiert werden.
export
(optional)
import
Modul-Partition attr
(optional)
;
|
|||||||||
/////// A-B.cpp export module A:B; ... /////// A-C.cpp module A:C; ... /////// A.cpp export module A; import :C; export import :B; ...
Alle Definitionen und Deklarationen in einer Modulpartition sind für die importierende Moduleinheit sichtbar, ob exportiert oder nicht.
Modulpartitionen können Modulschnittstelleneinheiten sein (wenn ihre Moduldeklarationen
export
enthalten). Sie müssen von der primären Modulschnittstelleneinheit export-importiert werden, und ihre exportierten Anweisungen werden sichtbar, wenn das Modul importiert wird.
export
(optional)
import
Modul-Partition attr
(optional)
;
|
|||||||||
/////// A.cpp export module A; // primäre Modulschnittstelleneinheit export import :B; // Hello() ist sichtbar beim Import von 'A'. import :C; // WorldImpl() ist jetzt nur für 'A.cpp' sichtbar. // export import :C; // FEHLER: Kann keine Modulimplementierungseinheit exportieren. // World() ist sichtbar für jede Übersetzungseinheit, die 'A' importiert. export char const* World() { return WorldImpl(); }
/////// A-B.cpp export module A:B; // Partition-Modul-Schnittstelleneinheit // Hello() ist für jede Übersetzungseinheit sichtbar, die 'A' importiert. export char const* Hello() { return "Hello"; }
/////// A-C.cpp module A:C; // Partition-Modul-Implementierungseinheit // WorldImpl() ist für jede Moduleinheit von 'A' sichtbar, die ':C' importiert. char const* WorldImpl() { return "World"; }
/////// main.cpp import A; import <iostream>; int main() { std::cout << Hello() << ' ' << World() << '\n'; // FEHLER: WorldImpl() ist nicht sichtbar. }
Modulverantwortung
Im Allgemeinen, wenn eine Deklaration nach der Modulerklärung in einer Moduleinheit erscheint, wird sie an dieses Modul angehängt .
Wenn eine Deklaration einer Entität an ein benanntes Modul angehängt ist, kann diese Entität nur in diesem Modul definiert werden. Alle Deklarationen einer solchen Entität müssen an dasselbe Modul angehängt sein.
Wenn eine Deklaration an ein benanntes Modul gebunden ist und nicht exportiert wird, hat der deklarierte Name Modulverknüpfung .
export module lib_A; int f() { return 0; } // f hat Modulverknüpfung export int x = f(); // x entspricht 0
export module lib_B; int f() { return 1; } // OK, f in lib_A und f in lib_B referenzieren verschiedene Entitäten export int y = f(); // y entspricht 1
Wenn zwei Deklarationen einer Entität verschiedenen Modulen zugeordnet sind, ist das Programm fehlerhaft; keine Diagnose ist erforderlich, falls keine von der anderen erreichbar ist.
/////// decls.h int f(); // #1, angehängt an das globale Modul int g(); // #2, angehängt an das globale Modul
/////// Modulschnittstelle von M module; #include "decls.h" export module M; export using ::f; // OK, deklariert keine Entität, exportiert #1 int g(); // Fehler: passt zu #2, aber an M gebunden export int h(); // #3 export int k(); // #4
/////// Andere Übersetzungseinheit import M; static int h(); // Fehler: entspricht #3 int k(); // Fehler: entspricht #4
Die folgenden Deklarationen sind keinem benannten Modul zugeordnet (und somit kann die deklarierte Entität außerhalb des Moduls definiert werden):
- namespace Definitionen mit externer Bindung;
- Deklarationen innerhalb einer language linkage Spezifikation.
export module lib_A; namespace ns // ns ist nicht an lib_A gebunden. { export extern "C++" int f(); // f ist nicht an lib_A gebunden. extern "C++" int g(); // g ist nicht an lib_A gebunden. export int h(); // h ist an lib_A gebunden. } // ns::h muss in lib_A definiert werden, aber ns::f und ns::g können anderswo definiert werden (z.B. // in einer traditionellen Quelldatei).
Hinweise
| Feature-Test Makro | Wert | Std | Funktion |
|---|---|---|---|
__cpp_modules
|
201907L
|
(C++20) | Module — Kernsprachunterstützung |
__cpp_lib_modules
|
202207L
|
(C++23) | Standardbibliotheksmodule std und std. compat |
Schlüsselwörter
private , module , import , export
Fehlerberichte
Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | Angewendet auf | Verhalten wie veröffentlicht | Korrektes Verhalten |
|---|---|---|---|
| CWG 2732 | C++20 |
es war unklar, ob importierbare Header
auf den Präprozessorzustand vom Importpunkt reagieren können |
keine Reaktion |
| P3034R1 | C++20 |
Modulnamen und Modulpartitionen könnten
als objektähnliche Makros definierte Bezeichner enthalten |
verboten |