Lernprogramm für benannte Module (C++)

In diesem Lernprogramm geht es um das Erstellen von C++20-Modulen. Module ersetzen Headerdateien. Sie erfahren, wie Module eine Verbesserung bei Headerdateien sind.

In diesem Tutorial lernen Sie, wie Sie:

  • Erstellen und Importieren eines Moduls
  • Erstellen einer primären Modulschnittstelleneinheit
  • Erstellen einer Modulpartitionsdatei
  • Erstellen einer Moduleinheitsimplementierungsdatei

Voraussetzungen

Dieses Lernprogramm erfordert Visual Studio 2022 17.1.0 oder höher.

Möglicherweise erhalten Sie IntelliSense-Fehler beim Arbeiten am Codebeispiel in diesem Lernprogramm. Die Arbeit am IntelliSense-Modul nimmt den Compiler auf. IntelliSense-Fehler können ignoriert werden und verhindern nicht, dass das Codebeispiel erstellt wird. Informationen zum Nachverfolgen des Fortschritts der IntelliSense-Arbeit finden Sie in diesem Problem.

Was sind C++-Module?

Headerdateien stellen fest, wie Deklarationen und Definitionen zwischen Quelldateien in C++ gemeinsam verwendet werden. Headerdateien sind zerbrechlich und schwierig zu verfassen. Sie können je nach der Reihenfolge, in die Sie sie einschließen, oder von den Makros, die definiert sind oder nicht definiert sind, unterschiedlich kompiliert werden. Sie können die Kompilierungszeit verlangsamen, da sie für jede Quelldatei verarbeitet werden, die sie enthält.

C++20 führt einen modernen Ansatz zur Komponente von C++-Programmen ein: Module.

Wie Headerdateien können Sie mithilfe von Modulen Deklarationen und Definitionen über Quelldateien hinweg freigeben. Im Gegensatz zu Headerdateien gehen jedoch keine Makrodefinitionen oder details zur privaten Implementierung verloren.

Module sind einfacher zu erstellen, da sich ihre Semantik aufgrund von Makrodefinitionen nicht ändert oder was sonst noch importiert wurde, die Reihenfolge der Importe usw. Sie erleichtern auch die Kontrolle darüber, was den Verbrauchern sichtbar ist.

Module sorgen für zusätzliche Sicherheit dafür, dass Headerdateien nicht funktionieren. Der Compiler und der Linker arbeiten zusammen, um mögliche Namenskonfliktprobleme zu verhindern und eine stärkere Definitionsregel (ODR) zu gewährleisten.

Ein starkes Besitzmodell verhindert Konflikte zwischen Namen zur Verknüpfungszeit, da der Linker exportierte Namen an das Modul anfügt, das sie exportiert. Mit diesem Modell kann der Microsoft Visual C++-Compiler ein nicht definiertes Verhalten verhindern, das durch Verknüpfen verschiedener Module verursacht wird, die ähnliche Namen im selben Programm melden. Weitere Informationen finden Sie unter "Starker Besitz".

Ein Modul besteht aus einer oder mehreren Quellcodedateien, die in eine Binärdatei kompiliert wurden. Die Binärdatei beschreibt alle exportierten Typen, Funktionen und Vorlagen im Modul. Wenn eine Quelldatei ein Modul importiert, liest der Compiler in der Binärdatei, die den Inhalt des Moduls enthält. Das Lesen der Binärdatei ist viel schneller als die Verarbeitung einer Headerdatei. Außerdem wird die Binärdatei jedes Mal, wenn das Modul importiert wird, vom Compiler wiederverwendet, was noch mehr Zeit spart. Da ein Modul einmal und nicht jedes Mal erstellt wird, kann die Buildzeit reduziert werden, manchmal dramatisch.

Wichtiger ist, dass Module nicht über die Fragilitätsprobleme verfügen, die Headerdateien ausführen. Beim Importieren eines Moduls werden die Semantik des Moduls oder die Semantik eines anderen importierten Moduls nicht geändert. Makros, Präprozessordirektiven und nicht exportierte Namen, die in einem Modul deklariert sind, sind für die Quelldatei, die sie importiert, nicht sichtbar. Sie können Module in beliebiger Reihenfolge importieren und die Bedeutung der Module nicht ändern.

Module können nebeneinander mit Headerdateien verwendet werden. Dieses Feature ist praktisch, wenn Sie eine Codebasis migrieren, um Module zu verwenden, da Sie dies in Phasen ausführen können.

In einigen Fällen kann eine Headerdatei als Kopfzeileneinheit und nicht als #include Datei importiert werden. Headereinheiten sind die empfohlene Alternative zu vorkompilierten Headerdateien (Precompiled Header, PCH). Sie sind einfacher einzurichten und zu verwenden als freigegebene PCH-Dateien , bieten aber ähnliche Leistungsvorteile. Weitere Informationen finden Sie unter Exemplarische Vorgehensweise: Erstellen und Importieren von Headereinheiten in Microsoft Visual C++.

Ihr Code kann Module im selben Projekt oder auf referenzierte Projekte automatisch verwenden, indem Projekt-zu-Projekt-Verweise auf statische Bibliotheksprojekte verwendet werden.

Erstellen des Projekts

Während wir ein einfaches Projekt erstellen, betrachten wir verschiedene Aspekte von Modulen. Das Projekt implementiert eine API mithilfe eines Moduls anstelle einer Headerdatei.

Wählen Sie in Visual Studio 2022 oder höher "Neues Projekt erstellen" und dann den Projekttyp "Konsolen-App " (für C++) aus. Wenn dieser Projekttyp nicht verfügbar ist, haben Sie möglicherweise nicht die Desktopentwicklung mit C++ -Workload ausgewählt, wenn Sie Visual Studio installiert haben. Sie können die Visual Studio-Installer verwenden, um die C++-Workload hinzuzufügen.

Geben Sie dem neuen Projekt den Namen ModulesTutorial , und erstellen Sie das Projekt.

Da Module ein C++20-Feature sind, verwenden Sie die Option oder /std:c++latest die /std:c++20 Compileroption. Klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf den ProjektnamenModulesTutorial, und wählen Sie dann "Eigenschaften" aus. Ändern Sie im Dialogfeld "Eigenschaftenseiten des Projekts" die Konfiguration in "Alle Konfigurationen " und "Plattform " in "Alle Plattformen". Wählen Sie im Strukturansichtsbereich auf der linken Seite "Konfigurationseigenschaften>allgemein" aus. Wählen Sie die C++-Sprachstandardeigenschaft aus. Verwenden Sie die Dropdownliste, um den Eigenschaftswert in ISO C++20 Standard (/std:c++20) zu ändern. Wählen Sie "OK" aus, um die Änderung anzunehmen.

A screenshot of the ModulesTutorial property page with the left pane open to Configuration Properties > General, and the C++ Language Standard dropdown open with ISO C++20 Standard (/std:c++20) selected

Erstellen der primären Modulschnittstelleneinheit

Ein Modul besteht aus einer oder mehreren Dateien. Eine dieser Dateien muss sein, was als primäre Modulschnittstelleneinheit bezeichnet wird. Es definiert, was das Modul exportiert; das heißt, welche Importeure des Moduls angezeigt werden. Pro Modul kann nur eine primäre Modulschnittstelleneinheit vorhanden sein.

Wenn Sie eine primäre Modulschnittstelleneinheit hinzufügen möchten, klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf "Quelldateien", und wählen Sie dann "Modul hinzufügen">aus.

Add item dialog in solution explorer with Add > Module... highlighted to illustrate where to click to add a module.

Geben Sie im daraufhin angezeigten Dialogfeld "Neues Element hinzufügen" dem neuen Modul den NamenBasicPlane.Figures.ixx, und wählen Sie "Hinzufügen" aus.

Der Standardinhalt der erstellten Moduldatei weist zwei Zeilen auf:

export module BasicPlane;

export void MyFunc();

Die export module Schlüsselwort (keyword) in der ersten Zeile deklarieren, dass diese Datei eine Modulschnittstelleneinheit ist. Hier gibt es einen subtilen Punkt: Für jedes benannte Modul muss genau eine Modulschnittstelleneinheit ohne Modulpartition angegeben sein. Diese Moduleinheit wird als primäre Modulschnittstelleneinheit bezeichnet.

In der primären Modulschnittstelleneinheit deklarieren Sie die Funktionen, Typen, Vorlagen, andere Module und Modulpartitionen, die beim Importieren des Moduls von Quelldateien verfügbar gemacht werden sollen. Ein Modul kann aus mehreren Dateien bestehen, aber nur die primäre Modulschnittstellendatei identifiziert, was verfügbar gemacht werden soll.

Ersetzen Sie den Inhalt der BasicPlane.Figures.ixx Datei durch:

export module BasicPlane.Figures; // the export module keywords mark this file as a primary module interface unit

Diese Zeile identifiziert diese Datei als primäre Modulschnittstelle und gibt dem Modul einen Namen: BasicPlane.Figures. Der Punkt im Modulnamen hat keine besondere Bedeutung für den Compiler. Ein Punkt kann verwendet werden, um zu vermitteln, wie Ihr Modul organisiert ist. Wenn Sie über mehrere Moduldateien verfügen, die zusammenarbeiten, können Sie Punkte verwenden, um eine Trennung von Bedenken anzugeben. In diesem Lernprogramm verwenden wir Zeiträume, um verschiedene Funktionsbereiche der API anzugeben.

Dieser Name ist auch der Ort, von dem der "benannte" in "benanntes Modul" stammt. Die Dateien, die Teil dieses Moduls sind, verwenden diesen Namen, um sich als Teil des benannten Moduls zu identifizieren. Ein benanntes Modul ist die Sammlung von Moduleinheiten mit demselben Modulnamen.

Wir sollten über die API sprechen, die wir für einen Moment implementieren werden, bevor wir fortfahren. Es wirkt sich auf die Entscheidungen aus, die wir als Nächstes treffen. Die API stellt unterschiedliche Formen dar. Wir werden nur einige Formen in diesem Beispiel bereitstellen: Point und Rectangle. Point soll als Teil komplexerer Formen verwendet werden, z Rectangle. B. .

Um einige Features von Modulen zu veranschaulichen, werden wir diese API in Teile einteilen. Ein Stück ist die Point API. Der andere Teil wird sein Rectangle. Stellen Sie sich vor, dass diese API in etwas komplexer wird. Die Abteilung ist nützlich, um Bedenken oder Beschleunigungscode Standard zu trennen.

Bisher haben wir die primäre Modulschnittstelle erstellt, die diese API verfügbar macht. Nun erstellen wir die Point API. Wir möchten, dass es Teil dieses Moduls ist. Aus Gründen der logischen Organisation und potenzieller Buildeffizienz möchten wir diesen Teil der API ganz einfach selbst verständlich machen. Dazu erstellen wir eine Modulpartitionsdatei .

Eine Modulpartitionsdatei ist ein Teil oder eine Komponente eines Moduls. Was es einzigartig macht, ist, dass es als einzelnes Stück des Moduls behandelt werden kann, aber nur innerhalb des Moduls. Modulpartitionen können nicht außerhalb eines Moduls verwendet werden. Modulpartitionen sind nützlich, um die Modulimplementierung in verwaltbare Teile aufzuteilen.

Wenn Sie eine Partition in das primäre Modul importieren, werden alle Deklarationen für das primäre Modul sichtbar, unabhängig davon, ob sie exportiert werden. Partitionen können in jede Partitionsschnittstelle, primäre Modulschnittstelle oder Moduleinheit importiert werden, die zum benannten Modul gehört.

Erstellen einer Modulpartitionsdatei

Point Modulpartition

Um eine Modulpartitionsdatei zu erstellen, klicken Sie im Projektmappen-Explorer mit der rechten Maustaste auf "Quelldateien", und wählen Sie dann "Modul hinzufügen">aus. Benennen Sie die DateiBasicPlane.Figures-Point.ixx, und wählen Sie "Hinzufügen" aus.

Da es sich um eine Modulpartitionsdatei ist, haben wir dem Modulnamen einen Bindestrich und den Namen der Partition hinzugefügt. Diese Konvention unterstützt den Compiler im Befehlszeilenfall, da der Compiler namenssuchregeln verwendet, die auf dem Modulnamen basieren, um die kompilierte .ifc Datei für die Partition zu finden. Auf diese Weise müssen Sie keine expliziten /reference Befehlszeilenargumente angeben, um die Partitionen zu finden, die zum Modul gehören. Es ist auch hilfreich, die Dateien zu organisieren, die zu einem Modul nach Namen gehören, da Sie leicht erkennen können, welche Dateien zu welchen Modulen gehören.

Ersetzen Sie den Inhalt von BasicPlane.Figures-Point.ixx durch Folgendes:

export module BasicPlane.Figures:Point; // defines a module partition, Point, that's part of the module BasicPlane.Figures

export struct Point
{
    int x, y;
};

Die Datei beginnt mit export module. Diese Schlüsselwort (keyword) sind auch der Beginn der primären Modulschnittstelle. Was diese Datei anders macht, ist der Doppelpunkt (:) nach dem Modulnamen, gefolgt vom Partitionsnamen. Diese Benennungskonvention identifiziert die Datei als Modulpartition. Da sie die Modulschnittstelle für eine Partition definiert, wird sie nicht als primäre Modulschnittstelle betrachtet.

Der Name BasicPlane.Figures:Point identifiziert diese Partition als Teil des Moduls BasicPlane.Figures. (Denken Sie daran, dass der Punkt im Namen keine besondere Bedeutung für den Compiler hat). Der Doppelpunkt gibt an, dass diese Datei eine Modulpartition mit dem Namen Point enthält, die zum Modul BasicPlane.Figuresgehört. Wir können diese Partition in andere Dateien importieren, die Teil dieses benannten Moduls sind.

In dieser Datei macht struct Point die export Schlüsselwort (keyword) den Verbrauchern sichtbar.

Rectangle Modulpartition

Die nächste Partition, die wir definieren, ist Rectangle. Erstellen Sie eine weitere Moduldatei mit den gleichen Schritten wie zuvor: Klicken Sie in Projektmappen-Explorer mit der rechten Maustaste auf "Quelldateien", und wählen Sie dann "Modul hinzufügen">aus. Geben Sie der Datei den Namen BasicPlane.Figures-Rectangle.ixx, und wählen Sie Hinzufügen aus.

Ersetzen Sie den Inhalt von BasicPlane.Figures-Rectangle.ixx durch Folgendes:

export module BasicPlane.Figures:Rectangle; // defines the module partition Rectangle

import :Point;

export struct Rectangle // make this struct visible to importers
{
    Point ul, lr;
};

// These functions are declared, but will
// be defined in a module implementation file
export int area(const Rectangle& r);
export int height(const Rectangle& r);
export int width(const Rectangle& r);

Die Datei beginnt mit export module BasicPlane.Figures:Rectangle; der deklariert eine Modulpartition, die Teil des Moduls BasicPlane.Figuresist. Der :Rectangle hinzugefügte Modulname definiert ihn als Partition des Moduls BasicPlane.Figures. Sie kann einzeln in jede der Moduldateien importiert werden, die Teil dieses benannten Moduls sind.

Im nächsten Schritt wird gezeigt, import :Point; wie eine Modulpartition importiert wird. Die import Anweisung macht alle exportierten Typen, Funktionen und Vorlagen in der Modulpartition für das Modul sichtbar. Sie müssen den Modulnamen nicht angeben. Der Compiler weiß, dass diese Datei zu dem BasicPlane.Figures Modul gehört, weil sie export module BasicPlane.Figures:Rectangle; oben in der Datei enthalten ist.

Als Nächstes exportiert der Code die Definition und struct Rectangle Deklaration für einige Funktionen, die verschiedene Eigenschaften des Rechtecks zurückgeben. Die export Schlüsselwort (keyword) gibt an, ob sie den Verbrauchern des Moduls vorausgeht. Es wird verwendet, um die Funktionen area, heightund width sichtbar außerhalb des Moduls zu machen.

Alle Definitionen und Deklarationen in einer Modulpartition sind für die importierende Moduleinheit sichtbar, unabhängig davon, ob sie über die export Schlüsselwort (keyword) verfügen oder nicht. Die export Schlüsselwort (keyword) bestimmt, ob die Definition, Deklaration oder Typedef außerhalb des Moduls sichtbar ist, wenn Sie die Partition in der primären Modulschnittstelle exportieren.

Namen werden für Verbraucher eines Moduls auf verschiedene Arten sichtbar gemacht:

  • Platzieren Sie die Schlüsselwort (keyword) export vor jedem Typ, jeder Funktion usw., die Sie exportieren möchten.
  • Wenn Sie zexport namespace N { ... }. B. vor einen Namespace setzenexport, wird alles, was in den geschweiften Klammern definiert ist, exportiert. Wenn Sie aber an anderer Stelle des Moduls, das Sie definieren namespace N { struct S {...};}, struct S nicht für Verbraucher des Moduls verfügbar ist. Es ist nicht verfügbar, da diese Namespacedeklaration nicht vorgestellt exportist, obwohl ein anderer Namespace mit demselben Namen vorhanden ist.
  • Wenn ein Typ, eine Funktion usw. nicht exportiert werden soll, lassen Sie die export Schlüsselwort (keyword) aus. Es ist für andere Dateien sichtbar, die Teil des Moduls sind, aber nicht für Importeure des Moduls.
  • Wird verwendet module :private; , um den Anfang der privaten Modulpartition zu markieren. Die private Modulpartition ist ein Abschnitt des Moduls, in dem Deklarationen nur für diese Datei sichtbar sind. Sie sind nicht für Dateien sichtbar, die dieses Modul oder andere Dateien importieren, die Teil dieses Moduls sind. Stellen Sie sich ihn als einen Abschnitt vor, der lokal für die Datei statisch ist. Dieser Abschnitt ist nur innerhalb der Datei sichtbar.
  • Verwenden Sie die Verwendung export import, um ein importiertes Modul oder eine importierte Modulpartition sichtbar zu machen. Ein Beispiel wird im nächsten Abschnitt gezeigt.

Verfassen der Modulpartitionen

Nachdem wir nun die beiden Teile der API definiert haben, bringen wir sie zusammen, damit Dateien, die dieses Modul importieren, als Ganzes darauf zugreifen können.

Alle Modulpartitionen müssen als Teil der Moduldefinition verfügbar gemacht werden, zu der sie gehören. Partitionen werden in der primären Modulschnittstelle verfügbar gemacht. Öffnen Sie die BasicPlane.Figures.ixx Datei, die die primäre Modulschnittstelle definiert. Ersetzen Sie den Inhalt durch:

export module BasicPlane.Figures; // keywords export module marks this as a primary module interface unit

export import :Point; // bring in the Point partition, and export it to consumers of this module
export import :Rectangle; // bring in the Rectangle partition, and export it to consumers of this module

Die beiden Zeilen, die beginnen export import , sind hier neu. Wenn diese beiden Schlüsselwort (keyword) kombiniert werden, weisen diese beiden Schlüsselwort (keyword) den Compiler an, das angegebene Modul zu importieren und für Verbraucher dieses Moduls sichtbar zu machen. In diesem Fall gibt der Doppelpunkt (:) im Modulnamen an, dass wir eine Modulpartition importieren.

Die importierten Namen enthalten nicht den vollständigen Modulnamen. Die Partition wurde z. B :Point . als export module BasicPlane.Figures:Pointdeklariert. Aber hier importieren :Pointwir . Da wir in der primären Modulschnittstellendatei für das Modul BasicPlane.Figuressind, wird der Modulname impliziert, und nur der Partitionsname wird angegeben.

Bisher haben wir die primäre Modulschnittstelle definiert, die die API-Oberfläche verfügbar macht, die wir zur Verfügung stellen möchten. Wir haben jedoch nur deklariert, nicht definiert, , area()oder height()width(). Als Nächstes erstellen wir eine Modulimplementierungsdatei.

Erstellen einer Moduleinheitsimplementierungsdatei

Implementierungsdateien für Moduleinheiten enden nicht mit einer .ixx Erweiterung – sie sind normale .cpp Dateien. Fügen Sie eine Implementierungsdatei für Moduleinheiten hinzu, indem Sie eine Quelldatei mit der rechten Maustaste in der Projektmappen-Explorer in "Quelldateien" erstellen, "Neues Element hinzufügen">und dann "C++-Datei (.cpp)" auswählen. Geben Sie der neuen Datei den NamenBasicPlane.Figures-Rectangle.cpp, und wählen Sie dann "Hinzufügen" aus.

Die Benennungskonvention für die Implementierungsdatei der Modulpartition folgt der Benennungskonvention für eine Partition. Sie hat jedoch eine .cpp Erweiterung, da es sich um eine Implementierungsdatei handelt.

Ersetzen Sie den Inhalt der BasicPlane.Figures-Rectangle.cpp Datei durch:

module;

// global module fragment area. Put #include directives here 

module BasicPlane.Figures:Rectangle;

int area(const Rectangle& r) { return width(r) * height(r); }
int height(const Rectangle& r) { return r.ul.y - r.lr.y; }
int width(const Rectangle& r) { return r.lr.x - r.ul.x; }

Diese Datei beginnt mit module; der Einführung eines speziellen Bereichs des Moduls, das als globales Modulfragment bezeichnet wird. Er steht vor dem Code für das benannte Modul und ist der Ort, an dem Sie Präprozessordirektiven verwenden können, z #include. B. . Code im globalen Modulfragment besitzt oder exportiert nicht die Modulschnittstelle.

Wenn Sie eine Headerdatei einschließen, soll sie in der Regel nicht als exportierter Teil des Moduls behandelt werden. In der Regel fügen Sie die Headerdatei als Implementierungsdetails hinzu, die nicht Teil der Modulschnittstelle sein sollten. Es gibt möglicherweise erweiterte Fälle, in denen dies erforderlich ist, aber im Allgemeinen nicht. Für Direktiven im globalen Modulfragment werden keine separaten Metadaten (.ifc Dateien) generiert #include . Globale Modulfragmente bieten einen guten Ort zum Einschließen von Headerdateien wie windows.h, oder auf Linux. unistd.h

Die Modulimplementierungsdatei, die wir erstellen, enthält keine Bibliotheken, da sie nicht als Teil der Implementierung benötigt werden. Aber wenn dies der Fall wäre, dann würde dieser Bereich die #include Richtlinien gehen.

Die Zeile module BasicPlane.Figures:Rectangle; gibt an, dass diese Datei Teil des benannten Moduls BasicPlane.Figuresist. Der Compiler bringt automatisch die Typen und Funktionen, die von der primären Modulschnittstelle verfügbar gemacht werden, in diese Datei ein. Eine Modulimplementierungseinheit verfügt nicht über die export Schlüsselwort (keyword) vor dem Schlüsselwort (keyword) in der module Moduldeklaration.

Als Nächstes sind die Definition der Funktionen area(), height()und width(). Sie wurden in der Rectangle Partition in BasicPlane.Figures-Rectangle.ixxdeklariert. Da die primäre Modulschnittstelle für dieses Modul die Point Partitionen und Rectangle Modulpartitionen importiert hat, sind diese Typen hier in der Moduleinheitsimplementierungsdatei sichtbar. Ein interessantes Feature der Modulimplementierungseinheiten: Der Compiler macht automatisch alles in der entsprechenden primären Modulschnittstelle für die Datei sichtbar. Nein imports <module-name> ist erforderlich.

Alles, was Sie in einer Implementierungseinheit deklarieren, ist nur für das Modul sichtbar, zu dem es gehört.

Importieren des Moduls

Jetzt verwenden wir das von uns definierte Modul. Öffnen Sie die Datei ModulesTutorial.cpp . Sie wurde automatisch als Teil des Projekts erstellt. Sie enthält derzeit die Funktion main(). Ersetzen Sie den Inhalt durch:

#include <iostream>

import BasicPlane.Figures;

int main()
{
    Rectangle r{ {1,8}, {11,3} };

    std::cout << "area: " << area(r) << '\n';
    std::cout << "width: " << width(r) << '\n';

    return 0;
}

Die Anweisung import BasicPlane.Figures; macht alle exportierten Funktionen und Typen aus dem BasicPlane.Figures Modul für diese Datei sichtbar. Es kann vor oder nach richtlinien #include kommen.

Die App verwendet dann die Typen und Funktionen aus dem Modul, um den Bereich und die Breite des definierten Rechtecks auszugeben:

area: 50
width: 10

Anatomie eines Moduls

Sehen wir uns nun die verschiedenen Moduldateien genauer an.

Primäre Modulschnittstelle

Ein Modul besteht aus einer oder mehreren Dateien. Einer von ihnen definiert die Schnittstelle, die Importeure sehen. Diese Datei enthält die Primäre Modulschnittstelle. Pro Modul kann nur eine primäre Modulschnittstelle vorhanden sein. Wie bereits erwähnt, gibt die exportierte Modulschnittstelleneinheit keine Modulpartition an.

Sie verfügt standardmäßig über eine .ixx Erweiterung. Sie können jedoch eine Quelldatei mit jeder Erweiterung als Modulschnittstellendatei behandeln. Legen Sie dazu die Eigenschaft "Kompilieren als" auf der Registerkarte "Erweitert" für die Eigenschaftenseite der Quelldatei auf "Kompilieren als Modul (/Schnittstelle)" fest:

Screenshot of a hypothetical source file's Configuration properties under Configuration properties > C/C++ > Advanced > Compile As, with Compile as C++ Module Code (/interface) highlighted

Die grundlegende Gliederung einer Modulschnittstellendefinitionsdatei lautet:

module; // optional. Defines the beginning of the global module fragment

// #include directives go here but only apply to this file and
// aren't shared with other module implementation files.
// Macro definitions aren't visible outside this file, or to importers.
// import statements aren't allowed here. They go in the module preamble, below.

export module [module-name]; // Required. Marks the beginning of the module preamble

// import statements go here. They're available to all files that belong to the named module
// Put #includes in the global module fragment, above

// After any import statements, the module purview begins here
// Put exported functions, types, and templates here

module :private; // optional. The start of the private module partition.

// Everything after this point is visible only within this file, and isn't 
// visible to any of the other files that belong to the named module.

Diese Datei muss entweder module; beginnen, um den Anfang des globalen Modulfragments anzugeben, oder export module [module-name]; um den Anfang der Modullöschansicht anzugeben.

Die Modul-Purview ist der Ort, an dem Funktionen, Typen, Vorlagen usw., von dem Modul verfügbar gemacht werden sollen.

Außerdem können Sie andere Module oder Modulpartitionen über die export import Schlüsselwort (keyword) verfügbar machen, wie in der BasicPlane.Figures.ixx Datei dargestellt.

Die primäre Schnittstellendatei muss alle Schnittstellenpartitionen exportieren, die für das Modul direkt oder indirekt definiert sind, oder das Programm ist fehlerhaft.

Die private Modulpartition ist der Ort, an dem Sie Elemente einfügen können, die nur in dieser Datei sichtbar sein sollen.

Modulschnittstelleneinheiten stellen die Schlüsselwort (keyword) module mit dem Schlüsselwort (keyword) exportvor.

Ausführlichere Informationen zur Modulsyntax finden Sie unter Module.

Modulimplementierungseinheiten

Modulimplementierungseinheiten gehören zu einem benannten Modul. Das benannte Modul, zu dem sie gehören, wird durch die module [module-name] Anweisung in der Datei angegeben. Modulimplementierungseinheiten enthalten Implementierungsdetails, die Sie aus Codehygiene oder anderen Gründen nicht in die primäre Modulschnittstelle oder in eine Modulpartitionsdatei einfügen möchten.

Modulimplementierungseinheiten sind nützlich, um ein großes Modul in kleinere Teile aufzuteilen, was zu schnelleren Buildzeiten führen kann. Diese Technik wird im Abschnitt "Bewährte Methoden" kurz behandelt.

Modulimplementierungseinheitsdateien haben eine .cpp Erweiterung. Die grundlegende Gliederung einer Modulimplementierungseinheitsdatei lautet:

// optional #include or import statements. These only apply to this file
// imports in the associated module's interface are automatically available to this file

module [module-name]; // required. Identifies which named module this implementation unit belongs to

// implementation

Modulpartitionsdateien

Modulpartitionen bieten eine Möglichkeit, ein Modul in verschiedene Teile oder Partitionen zu integrieren. Modulpartitionen sollen nur in Dateien importiert werden, die Teil des benannten Moduls sind. Sie können nicht außerhalb des benannten Moduls importiert werden.

Eine Partition verfügt über eine Schnittstellendatei und null oder mehr Implementierungsdateien. Eine Modulpartition teilt den Besitz aller Deklarationen im gesamten Modul.

Alle namen, die von Partitionsschnittstellendateien exportiert werden, müssen importiert und erneut (export import) durch die primäre Schnittstellendatei exportiert werden. Der Name einer Partition muss mit dem Modulnamen beginnen, gefolgt von einem Doppelpunkt und dem Namen der Partition.

Die grundlegende Gliederung einer Partitionsschnittstellendatei sieht wie folgt aus:

module; // optional. Defines the beginning of the global module fragment

// This is where #include directives go. They only apply to this file and aren't shared
// with other module implementation files.
// Macro definitions aren't visible outside of this file or to importers
// import statements aren't allowed here. They go in the module preamble, below

export module [Module-name]:[Partition name]; // Required. Marks the beginning of the module preamble

// import statements go here. 
// To access declarations in another partition, import the partition. Only use the partition name, not the module name.
// For example, import :Point;
// #include directives don't go here. The recommended place is in the global module fragment, above

// export imports statements go here

// after import, export import statements, the module purview begins
// put exported functions, types, and templates for the partition here

module :private; // optional. Everything after this point is visible only within this file, and isn't 
                         // visible to any of the other files that belong to the named module.
...

Bewährte Methoden des Moduls

Ein Modul und der Code, der sie importiert, muss mit denselben Compileroptionen kompiliert werden.

Modulbenennung

  • Sie können Punkte ('.' ) in Ihren Modulnamen verwenden, aber sie haben keine besondere Bedeutung für den Compiler. Verwenden Sie sie, um den Benutzern Ihres Moduls Bedeutung zu vermitteln. Beginnen Sie z. B. mit dem Bibliotheks- oder Projektanfangsnamespace. Beenden Sie den Namen, der die Funktionalität des Moduls beschreibt. BasicPlane.Figures soll eine API für geometrische Ebenen und insbesondere Abbildungen vermitteln, die auf einer Ebene dargestellt werden können.
  • Der Name der Datei, die die primäre Modulschnittstelle enthält, ist im Allgemeinen der Name des Moduls. Beispielsweise würde der Name der Datei, die die primäre Schnittstelle enthält, im Namen des Moduls BasicPlane.Figuresbenannt BasicPlane.Figures.ixx.
  • Der Name einer Modulpartitionsdatei ist in der Regel <primary-module-name>-<module-partition-name> der Name des Moduls, gefolgt von einem Bindestrich ('-') und dann dem Namen der Partition. Beispiel: BasicPlane.Figures-Rectangle.ixx

Wenn Sie über die Befehlszeile erstellen und diese Benennungskonvention für Modulpartitionen verwenden, müssen Sie nicht explizit für jede Modulpartitionsdatei hinzufügen /reference . Der Compiler sucht diese automatisch basierend auf dem Namen des Moduls. Der Name der kompilierten Partitionsdatei (endend mit einer .ifc Erweiterung) wird aus dem Modulnamen generiert. Berücksichtigen Sie den Modulnamen BasicPlane.Figures:Rectangle: Der Compiler erwartet, dass die entsprechende kompilierte Partitionsdatei Rectangle benannt BasicPlane.Figures-Rectangle.ifcist. Der Compiler verwendet dieses Benennungsschema, um die Verwendung von Modulpartitionen zu vereinfachen, indem automatisch die Schnittstelleneinheitsdateien für Partitionen gefunden werden.

Sie können sie mit Ihrer eigenen Konvention benennen. Anschließend müssen Sie jedoch entsprechende /reference Argumente für den Befehlszeilencompiler angeben.

Faktormodule

Verwenden Sie Modulimplementierungsdateien und Partitionen, um Ihr Modul für einfacheren Code Standard und potenziell schnellere Kompilierungszeiten zu berücksichtigen.

Beispielsweise bedeutet das Verschieben der Implementierung eines Moduls aus der Modulschnittstellendefinitionsdatei und in eine Modulimplementierungsdatei, dass Änderungen an der Implementierung nicht notwendigerweise dazu führen, dass jede Datei, die das Modul importiert, neu kompiliert wird (es sei denn, Sie verfügen inline über Implementierungen).

Modulpartitionen vereinfachen das logische Aufteilen eines großen Moduls. Sie können verwendet werden, um die Kompilierungszeit zu verbessern, sodass Änderungen an einem Teil der Implementierung nicht dazu führen, dass alle Dateien des Moduls neu kompiliert werden.

Zusammenfassung

In diesem Lernprogramm wurden Sie mit den Grundlagen von C++20-Modulen eingeführt. Sie haben eine primäre Modulschnittstelle erstellt, eine Modulpartition definiert und eine Modulimplementierungsdatei erstellt.

Siehe auch

Übersicht über Module in C++
module, importSchlüsselwort (keyword) export s
Eine Tour durch C++-Module in Visual Studio
Praktische C++20 Module und die Zukunft der Tools rund um C++-Module
Verschieben eines Projekts in C++ benannte Module
Exemplarische Vorgehensweise: Erstellen und Importieren von Headereinheiten in Microsoft Visual C++