C++20: Module strukturieren

Modernes C++  –  42 Kommentare

Wenn das Modul größer wird, sollte es in handliche Komponenten aufgeteilt werden. C++20 bietet dafür zwei Möglichkeiten an: Submodule und Partitionen. In diesem Artikel schaue ich mir beide Optionen genauer an.

Diesem Artikel möchte ich noch eine kurze Anmerkung vorausschicken. Der Einfachheit halber werde ich die Trennung von Module Interface Unit und Module Implementation Unit ignorieren. Das heißt, meine Module werden aus einer Datei bestehen. Zusätzlich werde ich keine Namensräume verwenden. Ich habe beide Features bereits in dem vorherigen Artikel: "C++20: Module Interface Unit und Module Implemenation Unit" vorgestellt.

Submodule sind einfach umzusetzen. Daher werde ich mit ihnen beginnen.

Submodule

Ein Modul kann Module importieren und diese wieder zurückexportieren. Im folgenden Beispiel importiert das Modul math die Submodule math.math1 und math.math2.

  • Modul math
// mathModule.ixx

export module math;

export import math.math1;
export import math.math2;

Der Ausdruck export import math.math1 importiert das Modul math.math1 und exportiert es als Bestandteil des Moduls math zurück.

Der Vollständigkeit halber sind hier die Module math.math1 und math.math2. Ich verwende einen Punkt, um das Modul von seinen Submodulen zu trennen. Er ist aber nicht notwendig.

  • Submodul math.math1
// mathModule1.ixx

export module math.math1; // (1)

export int add(int fir, int sec) { // (2)
return fir + sec;
}
  • Submodul math.math2
// mathModule2.ixx

export module math.math2; // (1)

export { // (2)
int mul(int fir, int sec) {
return fir * sec;
}
}

Wenn du die Submodule sorgfältig studierst, wirst du einen kleinen Unterschied zwischen der export-Anweisung (2) in den Modulen math.math1 und math.math2 feststellen. math.math1 verwendet einen export-Spezifizierer und math.math2 ein sogenannte export-Gruppe oder export-Block.

Aus der Sicht der Anwender ist die Verwendung des Moduls math einfach.

  • Client-Programm
// mathModuleClient.cpp

import std.core;
import math;

int main() {

std::cout << std::endl;

std::cout << "add(3, 4): " << add(3, 4) << std::endl;
std::cout << "mul(3, 4): " << mul(3, 4) << std::endl;

}

Das Kompilieren, Linken und Ausführen des Programms lässt sich mit der Microsoft-Implementierung von Modulen wie gewohnt umsetzen:

cl.exe /std:c++latest /c /experimental:module mathModule1.ixx /EHsc /MD  // (3)
cl.exe /std:c++latest /c /experimental:module mathModule2.ixx /EHsc /MD // (3)
cl.exe /std:c++latest /c /experimental:module mathModule.ixx /EHsc /MD // (3)
cl.exe /std:c++latest /experimental:module mathModuleClient.cpp mathModule1.obj mathModule2.obj mathModule.obj /EHsc /MD // (4)

Jeder Kompilierschritt (3) erzeugt zwei Artefakte: Die IFC-Datei (interface file) *.ifc, die implizit in (4) verwendet wird, und die *.obj-Datei, die explizit in (4) eingesetzt wird.

Ich habe bereits geschrieben, dass ein Submodul lediglich ein Modul ist. Jedes Submodul besitzt eine Modul-Deklaration (1). Konsequenterweise kann ich ein zweites Client-Programm implementieren, das nur das Modul math.math1 benötigt.

  • Zweites Client-Programm
// mathModuleClient1.cpp

import std.core;
import math.math1;

int main() {

std::cout << std::endl;

std::cout << "add(3, 4): " << add(3, 4) << std::endl;

}

Für dieses Programm ist es ausreichend, das neue Client-Programm zu kompilieren und zu linken. Das existierende Modul math.math1 lässt sich direkt dafür verwenden:

cl.exe /std:c++latest /experimental:module mathModuleClient1.cpp mathModule1.obj /EHsc /MD

Die Trennung von Modulen in Module und Submodule ist ein einfaches Mittel für Moduldesigner, Anwendern die Möglichkeit zu geben, die Module feingranular zu importieren. Diese Beobachtung gilt aber nicht für Modul-Partitionen.

Modul-Partitionen

Ein Modul lässt sich in Partitionen aufteilen. Jede Partition besteht aus einem Module Interface Unit (partition interface file) und keiner oder mehrerer Module Implementation Units (C++20: Module Interface Unit und Module Implementation Unit). Die Namen, die die Partitionen exportieren, werden durch das primäre Module Interface (primary modul interface oder primare interface file) importiert und zurückexportiert. Der Name einer Partition muss mit dem Namen des Moduls beginnen. Eine Partition kann nicht selbstständig existieren.

Leider ist die Beschreibung einer Modul-Partition deutlich komplizierter als ihre Umsetzung. In den folgenden Zeilen werde ich das Modul math und seine Submodule math.math1 und math.math2 in Modul-Partitionen transformieren. Bei diesem einfachen Vorgang verwende ich die soeben eingeführten Begriffe der Modul-Partition.

  • Das primäre Module Interface mathPartition.ixx
// mathPartition.ixx

export module math; // (1)

export import :math1; // (2)
export import :math2; // (2)

Das primäre Module Interface besteht aus der Modul-Deklaration (1). Es importiert und exportiert die Paritionen math1 und math2 mithilfe der Doppelpunkte zurück. Der Name der Partition muss mit dem Namen des Moduls beginnen. Konsequenterweise ist dieser daher im Ausdruck (2) nicht anzugeben.

  • Modul-Partionen (mathPartition1.ixx und mathPartition2.ixx)
export module math:math1;     // (1)   

export int add(int fir, int sec) {
return fir + sec;
}
// mathPartition2.ixx

export module math:math2; // (1)

export {
int mul(int fir, int sec) {
return fir * sec;
}
}

Analog zur Modul-Deklaration erklärt (1) eine sogenannte Module Interface Partition. Sie ist auch eine Module Interface Unit. Der Name math steht für den Namen des Moduls und die Namen math1 und math2 stehen für die der Partition.

  • Client-Programm
// mathModuleClient.cpp

import std.core;
import math;

int main() {

std::cout << std::endl;

std::cout << "add(3, 4): " << add(3, 4) << std::endl;
std::cout << "mul(3, 4): " << mul(3, 4) << std::endl;

}

Das Client-Programm ist identisch mit dem, das ich für Submodule verwendet habe. Die gleiche Aussage gilt für die Erzeugung des ausführbaren Programms:

cl.exe /std:c++latest /c /experimental:module mathPartition1.ixx /EHsc /MD
cl.exe /std:c++latest /c /experimental:module mathPartition2.ixx /EHsc /MD
cl.exe /std:c++latest /c /experimental:module mathPartition.ixx /EHsc /MD
cl.exe /std:c++latest /experimental:module mathModuleClient.cpp mathPartition1.obj mathPartition2.obj mathPartition.obj /EHsc /MD

Wie geht's weiter?

Module in C++20 haben noch mehr zu bieten. So führen sie zum Beispiel "Header Units" ein und unterscheiden zwischen dem globalen und privaten Modul-Fragment. Zuletzt möchte ich auf das Linken des Programms eingehen.

C++-Schulungen

Ich freue mich darauf, meine C++-Schulung anzubieten zu können.

Online-Schulung (Deutsch):

Präsenz-Schulung (Deutsch):

Online Seminars (English):

Mehr Informationen zu meinen Schulungen gibt es auf meiner deutschen (www.ModernesCpp.de) oder englischen Schulungsseite (www.ModerenesCpp.net).

Ich habe die Preise meine Online-Schulungen während der Corona-Krise deutlich reduziert. Wem der Preis noch zu hoch ist, der kann direkt mit mir (schulung@ModernesCpp.de) Kontakt aufnehmen. Dies Angebot gilt natürlich auch für Firmen.