Freigeben über


Übersicht über Module in C++

C++20 führt Module ein. Ein Modul ist eine Reihe von Quellcodedateien, die unabhängig von den Quelldateien kompiliert werden (oder genauer gesagt, die Übersetzungseinheiten , die sie importieren.

Module beseitigen oder reduzieren viele der Probleme, die mit der Verwendung von Headerdateien verbunden sind. Sie reduzieren häufig Kompilierungszeiten, manchmal erheblich. Makros, Präprozessordirektiven und nicht exportierte Namen, die in einem Modul deklariert sind, sind außerhalb des Moduls nicht sichtbar. Sie haben keine Auswirkungen auf die Kompilierung der Übersetzungseinheit, die das Modul importiert. Sie können Module in beliebiger Reihenfolge importieren, ohne sich um Makrodefinitionen zu sorgen. Deklarationen in der importierenden Übersetzungseinheit nehmen nicht an der Überladungsauflösung oder der Namenssuche im importierten Modul teil. Nachdem ein Modul einmal kompiliert wurde, werden die Ergebnisse in einer Binärdatei gespeichert, die alle exportierten Typen, Funktionen und Vorlagen beschreibt. Der Compiler kann diese Datei viel schneller verarbeiten als eine Headerdatei. Und der Compiler kann ihn an jedem Ort wiederverwenden, an dem das Modul in ein Projekt importiert wird.

Sie können Module nebeneinander mit Headerdateien verwenden. Eine C++-Quelldatei kann import Module und auch #include Headerdateien enthalten. In einigen Fällen können Sie eine Headerdatei als Modul importieren, was schneller ist, als sie mit dem Präprozessor mithilfe von #include zu verarbeiten. Es wird empfohlen, Module in neuen Projekten anstelle von Headerdateien so weit wie möglich zu verwenden. Experimentieren Sie bei größeren existierenden Projekten, die sich in aktiver Entwicklung befinden, mit der Umwandlung von Legacy-Headern in Module. Richten Sie Ihre Entscheidung danach, ob sich die Kompilierungszeiten deutlich verkürzen.

Informationen zum Kontrast von Modulen mit anderen Methoden zum Importieren der Standardbibliothek finden Sie unter Vergleichen von Headereinheiten, Modulen und vorkompilierten Headern.

Ab Visual Studio 2022, Version 17.5, wird das Importieren der Standardbibliothek als Modul sowohl standardisiert als auch vollständig im Microsoft C++-Compiler implementiert. Informationen zum Importieren der Standardbibliothek mithilfe von Modulen finden Sie unter Importieren der C++-Standardbibliothek mithilfe von Modulen.

Einzelpartitionsmodule

Ein Einzelpartitionsmodul ist ein Modul, das aus einer einzelnen Quelldatei besteht. Die Modulschnittstelle und -implementierung befinden sich in derselben Datei.

Das folgende Beispiel für ein Partitionsmodul zeigt eine einfache Moduldefinition in einer Quelldatei namens Example.ixx. Die .ixx Erweiterung ist die Standarderweiterung für Modulschnittstellendateien in Visual Studio. Wenn Sie eine andere Erweiterung verwenden möchten, verwenden Sie den Schalter "/interface" , um sie als Modulschnittstelle zu kompilieren. In diesem Beispiel enthält die Schnittstellendatei sowohl die Funktionsdefinition als auch die Deklaration. Sie können die Definitionen auch in einer oder mehreren separaten Modulimplementierungsdateien platzieren, wie in einem späteren Beispiel gezeigt, aber dies ist ein Beispiel für ein Einzelpartitionsmodul.

Die Anweisung export module Example; gibt an, dass diese Datei die primäre Schnittstelle für ein Modul namens Example ist. Der export-Modifizierer vor int f() gibt an, dass diese Funktion sichtbar ist, wenn ein anderes Programm oder Modul Example importiert:

// Example.ixx
export module Example;

#define ANSWER 42

namespace Example_NS
{
   int f_internal()
   {
     return ANSWER;
   }

   export int f()
   {
     return f_internal();
   }
}

Die Datei MyProgram.cpp verwendet import für den Zugriff auf den Namen, der von Example exportiert wird. Der Namespacename Example_NS ist hier sichtbar, aber nicht alle seine Elemente, da sie nicht exportiert sind. Außerdem ist das Makro ANSWER nicht sichtbar, da Makros nicht exportiert werden.

// MyProgram.cpp
import std;
import Example;

using namespace std;

int main()
{
   cout << "The result of f() is " << Example_NS::f() << endl; // 42
   // int i = Example_NS::f_internal(); // C2039
   // int j = ANSWER; //C2065
}

Die import Deklaration kann nur auf globaler Ebene angezeigt werden. Ein Modul und der Code, der sie verwendet, muss mit denselben Compileroptionen kompiliert werden.

Modulgrammatik

module-name:
module-name-qualifier-seqwählenidentifier

module-name-qualifier-seq:
identifier.
module-name-qualifier-seqidentifier.

module-partition:
:module-name

module-declaration:
exportoptierenmodulemodule-namemodule-partitionoptierenattribute-specifier-seqoptieren;

module-import-declaration:
exportoptierenimportmodule-nameattribute-specifier-seqoptieren;
exportoptierenimportmodule-partitionattribute-specifier-seqoptieren;
exportoptierenimportheader-nameattribute-specifier-seqoptieren;

Implementieren von Modulen

Eine Modulschnittstelle exportiert den Modulnamen und alle Namespaces, Typen, Funktionen usw., die die öffentliche Schnittstelle des Moduls bilden.
Eine Modulimplementierung definiert die vom Modul exportierten Elemente.
In seiner einfachsten Form kann ein Modul eine einzelne Datei sein, die die Modulschnittstelle und -implementierung kombiniert. Sie können die Implementierung auch in eine oder mehrere separate Modulimplementierungsdateien einfügen, ähnlich wie .h und .cpp Dateien dies tun.

Bei größeren Modulen können Sie Teile des Moduls in Untermodule unterteilen, die als Partitionen bezeichnet werden. Jede Partition besteht aus einer Modulschnittstellendatei, die den Modulpartitionsnamen exportiert. Eine Partition kann auch über eine oder mehrere Partitionsimplementierungsdateien verfügen. Das Modul insgesamt verfügt über eine primäre Modulschnittstelle, die die öffentliche Schnittstelle des Moduls ist. Sie kann die Partitionsschnittstellen bei Bedarf exportieren.

Ein Modul besteht aus einer oder mehreren Moduleinheiten. Eine Moduleinheit ist eine Übersetzungseinheit (eine Quelldatei), die eine Moduldeklaration enthält. Es gibt mehrere Typen von Moduleinheiten:

  • Eine Modulschnittstelleneinheit exportiert einen Modulnamen oder einen Modulpartitionsnamen. Eine Modulschnittstelleneinheit weist export module in der Moduldeklaration auf.
  • Eine Modulimplementierungseinheit exportiert keinen Modulnamen oder Modulpartitionsnamen. Wie der Name schon sagt, implementiert es ein Modul.
  • Eine primäre Modulschnittstelleneinheit exportiert den Modulnamen. Es muss eine und nur eine primäre Modulschnittstelleneinheit in einem Modul vorhanden sein.
  • Eine Modulpartitionseinheit exportiert einen Modulpartitionsnamen.
  • Eine Modulpartitionsimplementierungseinheit hat einen Modulpartitionsnamen in der Moduldeklaration, aber kein export Schlüsselwort.

Das export Schlüsselwort wird nur in Schnittstellendateien verwendet. Eine Implementierungsdatei kann import ein anderes Modul, aber keine export Namen haben. Implementierungsdateien können eine beliebige Erweiterung haben.

Module, Namespaces und argumentabhängige Suche

Die Regeln für Namespaces in Modulen sind identisch mit jedem anderen Code. Wenn eine Deklaration innerhalb eines Namespace exportiert wird, wird der eingeschlossene Namespace (ausgenommen Elemente, die nicht explizit in diesen Namespace exportiert werden) ebenfalls implizit exportiert. Wenn ein Namespace explizit exportiert wird, werden alle Deklarationen innerhalb dieser Namespacedefinition exportiert.

Wenn der Compiler eine argumentabhängige Suche zur Überladungsauflösung in der importierenden Unit durchführt, berücksichtigt er Funktionen, die in derselben Unit (einschließlich Modulschnittstellen) deklariert sind, in der der Typ der Funktionsargumente definiert ist.

Modulpartitionen

Eine Modulpartition ähnelt einem Modul, außer:

  • Sie teilt die Eigentümerschaft an allen Deklarationen im gesamten Modul.
  • Alle Namen, die von den Partitionsschnittstellendateien exportiert werden, werden von der primären Schnittstellendatei importiert und exportiert.
  • Der Name einer Partition muss mit dem Modulnamen beginnen, gefolgt von einem Doppelpunkt (:).
  • Deklarationen in einer der Partitionen sind innerhalb des gesamten Moduls sichtbar.
  • Es sind keine besonderen Vorsichtsmaßnahmen erforderlich, um OdR-Fehler (One Definition-Rule) zu vermeiden. Sie können einen Namen (Funktion, Klasse usw.) in einer Partition deklarieren und in einer anderen definieren.

Eine Partitionsimplementierungsdatei beginnt wie folgt und ist nach C++-Standards eine interne Partition.

module Example:part1;

Eine Partitionsschnittstellendatei beginnt wie folgt:

export module Example:part1;

Um auf Deklarationen in einer anderen Partition zuzugreifen, muss eine Partition sie importieren. Sie kann jedoch nur den Partitionsnamen und nicht den Modulnamen verwenden:

module Example:part2;
import :part1;

Die primäre Schnittstelleneinheit muss alle Schnittstellenpartitionsdateien des Moduls wie folgt importieren und erneut exportieren:

export import :part1;
export import :part2;

Die primäre Schnittstelleneinheit kann Partitionsimplementierungsdateien importieren, aber nicht exportieren. Diese Dateien dürfen keine Namen exportieren. Mit dieser Einschränkung kann ein Modul implementierungsinterne Details für das Modul beibehalten.

Module und Headerdateien

Sie können Headerdateien in eine Modulquelldatei einfügen, indem Sie eine #include Direktive vor der Moduldeklaration einfügen. Diese Dateien werden als Teil des globalen Modulfragments betrachtet. Ein Modul kann nur die Namen im globalen Modulfragment sehen, die sich in den Headern befinden, die es explizit enthält. Das globale Modulfragment enthält nur Symbole, die verwendet werden.

// MyModuleA.cpp

#include "customlib.h"
#include "anotherlib.h"

import std;
import MyModuleB;

//... rest of file

Sie können eine herkömmliche Headerdatei verwenden, um zu steuern, welche Module importiert werden:

// MyProgram.h
#ifdef C_RUNTIME_GLOBALS
import std.compat;
#else
import std;
#endif

Importierte Headerdateien

Einige Kopfzeilen sind ausreichend eigenständig, dass sie mit dem import Schlüsselwort eingebracht werden können. Der Hauptunterschied zwischen einem importierten Header und einem importierten Modul besteht darin, dass alle Präprozessordefinitionen im Header unmittelbar nach der import Anweisung im Importprogramm sichtbar sind.

import <vector>;
import "myheader.h";

Siehe auch

Importieren der C++-Standardbibliothek mithilfe von Modulen
module, importexport
Anleitung für benannte Module
Vergleichen von Kopfzeileneinheiten, Modulen und vorkompilierten Headern