Module (seit 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 prominent, ein Beispiel ist die Standardbibliothek, deren Deklarationen durch Einbinden des entsprechenden Headers bereitgestellt werden können.
Module sind ein Sprachmerkmal zum Teilen von Deklarationen und Definitionen über Übersetzungseinheiten hinweg. Sie sind eine Alternative zu einigen Anwendungsfällen von Headern.
Module sind orthogonal zu Namespaces.
// helloworld.cpp export module helloworld; // module declaration import <iostream>; // import declaration export void hello() // export declaration { std::cout << "Hello world!\n"; }
// main.cpp import helloworld; // import declaration int main() { hello(); }
Inhalt |
[bearbeiten] Syntax
export(optional) module modulname modulpartition (optional) attr (optional) ; |
(1) | ||||||||
export deklaration |
(2) | ||||||||
export { deklarationssequenz (optional) } |
(3) | ||||||||
export(optional) import modulname attr (optional) ; |
(4) | ||||||||
export(optional) import modulpartition attr (optional) ; |
(5) | ||||||||
export(optional) import headername attr (optional) ; |
(6) | ||||||||
module;
|
(7) | ||||||||
module : private;
|
(8) | ||||||||
[bearbeiten] Moduldeklarationen
Eine Übersetzungseinheit kann eine Moduldeklaration haben, in diesem Fall gilt sie als Moduleinheit. Die Moduldeklaration muss, falls vorhanden, die erste Deklaration der Übersetzungseinheit sein (außer dem globalen Modulfragment, das später behandelt wird). Jede Moduleinheit ist einem Modulnamen (und optional einer Partition) zugeordnet, der in der Moduldeklaration angegeben ist.
export(optional) module modulname modulpartition (optional) attr (optional) ; |
|||||||||
Der Modulname besteht aus einem oder mehreren durch Punkte getrennten Bezeichnern (z. B.: mymodule, mymodule.mysubmodule, mymodule2...). Punkte haben keine intrinsische Bedeutung, werden aber informell zur Darstellung von Hierarchien verwendet.
Wenn ein Bezeichner im Modulnamen oder in der Modulpartition als objektartiges Makro definiert ist, ist das Programm ill-formed.
Ein benanntes Modul ist die Sammlung von Moduleinheiten mit demselben Modulnamen.
Moduleinheiten, deren Deklaration das Schlüsselwort export enthält, werden als Modulschnittstelleneinheiten bezeichnet; alle anderen Moduleinheiten werden als Modulimplementierungseinheiten 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 Importieren des entsprechenden benannten Moduls verfügbar sein.
// (each line represents a separate translation unit) export module A; // declares the primary module interface unit for named module 'A' module A; // declares a module implementation unit for named module 'A' module A; // declares another module implementation unit for named module 'A' export module A.B; // declares the primary module interface unit for named module 'A.B' module A.B; // declares a module implementation unit for named module 'A.B'
[bearbeiten] 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, stellen Sie ihr entweder das Schlüsselwort export voran oder platzieren Sie sie innerhalb eines export-Blocks.
export deklaration |
|||||||||
export { deklarationssequenz (optional) } |
|||||||||
export module A; // declares the primary module interface unit for named module 'A' // hello() will be visible by translations units importing 'A' export char const* hello() { return "hello"; } // world() will NOT be visible. char const* world() { return "world"; } // Both one() and zero() will be visible. export { int one() { return 1; } int zero() { return 0; } } // Exporting namespaces also works: hi::english() and hi::french() will be visible. export namespace hi { char const* english() { return "Hi!"; } char const* french() { return "Salut!"; } }
[bearbeiten] Importieren von Modulen und Header-Einheiten
Module werden über eine Importdeklaration importiert.
export(optional) import modulname attr (optional) ; |
|||||||||
Alle Deklarationen und Definitionen, die in den Modulschnittstelleneinheiten des gegebenen benannten Moduls exportiert werden, sind in der Übersetzungseinheit, die die Importdeklaration verwendet, verfügbar.
Importdeklarationen können in einer Modulschnittstelleneinheit exportiert werden. Das heißt, wenn Modul B A export-importiert, dann macht das Importieren von B auch alle Exporte von A sichtbar.
In Moduleinheiten müssen alle Importdeklarationen (einschließlich Export-Importe) nach der Moduldeklaration und vor allen anderen Deklarationen gruppiert werden.
/////// A.cpp (primary module interface unit of 'A') export module A; export char const* hello() { return "hello"; } /////// B.cpp (primary module interface unit of 'B') export module B; export import A; export char const* world() { return "world"; } /////// main.cpp (not a module unit) #include <iostream> import B; int main() { std::cout << hello() << ' ' << world() << '\n'; }
#include sollte nicht in einer Moduleinheit verwendet werden (außerhalb des globalen Modulfragments), da alle inkludierten Deklarationen und Definitionen als Teil des Moduls betrachtet würden. Stattdessen können Header auch als Header-Einheiten mit einer Importdeklaration importiert werden.
export(optional) import headername attr (optional) ; |
|||||||||
Eine Header-Einheit ist eine separate Übersetzungseinheit, die aus einem Header synthetisiert wird. Das Importieren einer Header-Einheit macht alle ihre Definitionen und Deklarationen zugänglich. Präprozessor-Makros sind ebenfalls zugänglich (da Importdeklarationen vom Präprozessor erkannt werden).
Im Gegensatz zu #include beeinflussen jedoch bereits am Punkt der Importdeklaration definierte Präprozessor-Makros nicht die Verarbeitung des Headers. Dies kann in einigen Fällen unbequem sein (einige Header verwenden Präprozessor-Makros als eine Form der Konfiguration), in diesem Fall ist die Verwendung des globalen Modulfragments erforderlich.
/////// A.cpp (primary module interface unit of 'A') export module A; import <iostream>; export import <string_view>; export void print(std::string_view message) { std::cout << message << std::endl; } /////// main.cpp (not a module unit) import A; int main() { std::string_view message = "Hello, world!"; print(message); }
[bearbeiten] Globales Modulfragment
Moduleinheiten können mit einem globalen Modulfragment präfixiert werden, das 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äprozessor-direktiven (optional) modul-deklaration |
|||||||||
Wenn eine Moduleinheit ein globales Modulfragment hat, muss ihre erste Deklaration module; lauten. Dann können nur Präprozessor-Direktiven im globalen Modulfragment erscheinen. Dann kennzeichnet eine Standard-Moduldeklaration das Ende des globalen Modulfragments und den Beginn des Modulinhalts.
/////// A.cpp (primary module interface unit of 'A') module; // Defining _POSIX_C_SOURCE adds functions to standard headers, // according to the POSIX standard. #define _POSIX_C_SOURCE 200809L #include <stdlib.h> export module A; import <ctime>; // Only for demonstration (bad source of randomness). // Use C++ <random> instead. export double weak_random() { std::timespec ts; std::timespec_get(&ts, TIME_UTC); // from <ctime> // Provided in <stdlib.h> according to the POSIX standard. srand48(ts.tv_nsec); // drand48() returns a random number between 0 and 1. return drand48(); } /////// main.cpp (not a module unit) import <iostream>; import A; int main() { std::cout << "Random value between 0 and 1: " << weak_random() << '\n'; }
[bearbeiten] Privates Modulfragment
Die primäre Modulschnittstelleneinheit kann mit einem privaten Modulfragment suffigiert werden, das es ermöglicht, ein Modul als einzelne Übersetzungseinheit darzustellen, ohne den gesamten Inhalt des Moduls für Importierende erreichbar zu machen.
module : private;
deklarationssequenz (optional) |
|||||||||
Privates Modulfragment beendet den Teil der Modulschnittstelleneinheit, der das Verhalten anderer Übersetzungseinheiten beeinflussen kann. Wenn eine Moduleinheit ein privates Modulfragment enthält, ist sie die einzige Moduleinheit ihres Moduls.
export module foo; export int f(); module : private; // ends the portion of the module interface unit that // can affect the behavior of other translation units // starts a private module fragment int f() // definition not reachable from importers of foo { return 42; }
[bearbeiten] 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; // Declares a module interface unit for module 'A', partition ':B'.
Eine Modulpartition repräsentiert genau eine Moduleinheit (zwei Moduleinheiten können nicht dieselbe Modulpartition bezeichnen). Sie sind nur 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 modulpartition 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, unabhängig davon, ob sie exportiert wurden oder nicht.
Modulpartitionen können Modulschnittstelleneinheiten sein (wenn ihre Moduldeklarationen export haben). 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 modulpartition attr (optional) ; |
|||||||||
/////// A.cpp export module A; // primary module interface unit export import :B; // Hello() is visible when importing 'A'. import :C; // WorldImpl() is now visible only for 'A.cpp'. // export import :C; // ERROR: Cannot export a module implementation unit. // World() is visible by any translation unit importing 'A'. export char const* World() { return WorldImpl(); }
/////// A-B.cpp export module A:B; // partition module interface unit // Hello() is visible by any translation unit importing 'A'. export char const* Hello() { return "Hello"; }
/////// A-C.cpp module A:C; // partition module implementation unit // WorldImpl() is visible by any module unit of 'A' importing ':C'. char const* WorldImpl() { return "World"; }
/////// main.cpp import A; import <iostream>; int main() { std::cout << Hello() << ' ' << World() << '\n'; // WorldImpl(); // ERROR: WorldImpl() is not visible. }
[bearbeiten] Modulbesitz
Im Allgemeinen gilt: Wenn eine Deklaration nach der Moduldeklaration in einer Moduleinheit erscheint, ist sie diesem Modul zugeordnet.
Wenn eine Deklaration einer Entität einem benannten Modul zugeordnet ist, kann diese Entität nur in diesem Modul definiert werden. Alle Deklarationen einer solchen Entität müssen demselben Modul zugeordnet sein.
Wenn eine Deklaration einem benannten Modul zugeordnet ist und nicht exportiert wird, hat der deklarierte Name eine Modulbindung.
export module lib_A; int f() { return 0; } // f has module linkage export int x = f(); // x equals 0
export module lib_B; int f() { return 1; } // OK, f in lib_A and f in lib_B refer to different entities export int y = f(); // y equals 1
Wenn zwei Deklarationen einer Entität unterschiedlichen Modulen zugeordnet sind, ist das Programm ill-formed; keine Diagnose ist erforderlich, wenn keine der beiden von der anderen erreichbar ist.
/////// decls.h int f(); // #1, attached to the global module int g(); // #2, attached to the global module
/////// Module interface of M module; #include "decls.h" export module M; export using ::f; // OK, does not declare an entity, exports #1 int g(); // Error: matches #2, but attached to M export int h(); // #3 export int k(); // #4
/////// Other translation unit import M; static int h(); // Error: matches #3 int k(); // Error: matches #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 Sprachbindungs-Spezifikation.
export module lib_A; namespace ns // ns is not attached to lib_A. { export extern "C++" int f(); // f is not attached to lib_A. extern "C++" int g(); // g is not attached to lib_A. export int h(); // h is attached to lib_A. } // ns::h must be defined in lib_A, but ns::f and ns::g can be defined elsewhere (e.g. // in a traditional source file).
[bearbeiten] Anmerkungen
| Feature-Test-Makro | Wert | Std | Feature |
|---|---|---|---|
__cpp_modules |
201907L |
(C++20) | Module — Kernsprache-Unterstützung |
__cpp_lib_modules |
202207L |
(C++23) | Standardbibliotheksmodule std und std.compat |
[bearbeiten] Schlüsselwörter
private, module, import, export
[bearbeiten] Fehlerberichte
Die folgenden Verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.
| DR | angewendet auf | Verhalten wie veröffentlicht | Korrigiertes Verhalten |
|---|---|---|---|
| CWG 2732 | C++20 | war unklar, ob importierbare Header auf den Präprozessorzustand vom Importpunkt aus reagieren können |
keine Reaktion |
| P3034R1 | C++20 | Modulnamen und Modulpartitionen könnten Bezeichner enthalten, die als objektartige Makros definiert sind |
verboten |