Wird ein Programm umfangreicher, wird es in logische Blöcke unterteilt. Jeder dieser Blöcke besteht aus einem Implementierungsteil, der Quellcode-Datei .cpp, und dem Schnittstellenteil, der Header-Datei .h. Der Schnittstellenteil enthält nur die Deklarationen der zu veröffentlichen globalen Funktionen sowie die extern-Deklarationen der zu veröffentlichen Daten.
Das nachfolgende Beispiel zeigt anhand einer kleinen Bibliothek bibl die Aufteilung des Codes in einen Schnittstellenteil und Implementierungsteil.
// Datei bibl.h
// Schnittstellen der Bibliothek bibl.h
// Zu veroeffentliche Daten und Funktionen
extern const char *const pText;
float CircleArea(float rad);
// Datei bibl.cpp
// Implementierung der Bibliothek bibl.cpp
#include "bibl.h"
// Globales const-Datum initialisieren
const char *const pText = "bibl";
// Lokale Konstante
constexpr float PI = 3.1416f;
// Lokale Funktion
float Quad(float val)
{
return val * val;
}
// Globale Funktion
float CircleArea(float rad)
{
return PI * Quad(rad);
}
// Datei main.cpp
// Anwendung
#include <iostream>
#include <format>
#include "bibl.h" // Bibliotheks-Schnittstellen einbinden
int main()
{
// Bibliotheksname ausgeben
std::cout << std::format("Bibliothek: {}\n",pText);
// Kreisflaeche berechnen
float radius = 2.2f;
std::cout << std::format("Kreisradius: {}, ergibt Kreisflaeche: {}\n", radius, CircleArea(radius));
}
Einer der Nachteile der Aufteilung des Codes in einen Schnittstellenteil und Implementierungsteil ist, dass bei Änderungen an den Schnittstellen zwei Dateien anzupassen sind.
Des Weiteren werden die Schnittstellendateien beim Einbinden in eine Quellcode-Datei jedes Mal neu vom Compiler übersetzt und dies kann bei umfangreichen Schnittstellendateien erhebliche Zeit beanspruchen. Um u.a. diese Nachteile zu umgehen, wurden mit C++20 die sogenannten Module eingeführt.
In einem Modul können sowohl den Schnittstellenteil wie auch Implementierungsteil zusammengefasst werden. Eine Modul-Datei durch die die Anweisung
export module ModName;
definiert, wobei ModName der frei wählbare Name des Moduls ist. Vor dieser Anweisung dürfen nur Kommentare und die Definition des globalen Moduls (wird gleich noch erläutert) stehen.
Die zu veröffentlichen Daten und Funktionen werden durch voranstellen des Schlüsselworts export vor dem Datentyp bzw. dem Returntyp definiert.
Die kleine Bibliothek als Modul würde damit wie folgt aussehen:
// Implementierung der Bibliothek bibl.cxx als Modul
export module Bibl;
// Zu veroeffentliches const-Datum initialisieren
export const char *pText = "bibl";
// Nicht zu veroeffentliche Konstante
constexpr float PI = 3.1416f;
// Lokale Funktion
float Quad(float val)
{
return val * val;
}
// Zu veroeffentliche Funktion
export float CircleArea(float rad)
{
return PI * Quad(rad);
}
Modul-Dateien müssen vor den sonstigen Quellcode-Dateien vom Compiler übersetzt werden, damit er die zu exportierenden Namen extrahieren kann. Je nach Compiler erfolgt dies bis zum jetzigen Zeitpunkt unterschiedlich:
Im Projektmappen-Explorer die Eigenschaften der Modul-Datei öffnen. Anschließend auf die Eigenschaft C/C++ – Erweitert klicken und auf der rechten Seite unter Kompilierungsart die Option Als C++-Modulcode kompilieren auswählen.
Hier sind keine weiteren Einstellungen notwendig, da diese bereits bei der Konfiguration des Compilers unter CodeBlocks entsprechend vorgenommen wurden. Sie müssen nur explizit im Workspace durch Auswahl der Option Build file die Modul-Datei vor der Quellcode-Datei übersetzen.
Um auf die exportierten Daten und Funktionen aus einer Modul-Datei zuzugreifen, wird das Modul mit der Anweisung
import ModName;
importiert.
// Anwendung
#include <iostream>
#include <format>
// Modul einbinden
import Bibl;
int main()
{
// Bibliotheksname ausgeben
std::cout << std::format("Bibliothek: {}\n",pText);
// Kreisflaeche berechnen
float radius = 2.2f;
std::cout << std::format("Kreisradius: {}, ergibt Kreisflaeche: {}\n", radius, CircleArea(radius));
}
Stehen in einer Modul-Datei #include-Anweisungen, müssen diese im globalen Modul eingebunden werden. Das globale Modul wird durch die Anweisung
module;
definiert und muss vor der export module Anweisung stehen. Im globalen Modul dürfen nur #include-Anweisungen und Kommentare stehen.
// Bibliotheksdatei bibl.cxx
// =========================
// Globales Modul fuer includes
module;
#include <iostream>
#include <format>
// Modul exportieren
export module Checksum;
// Berechnung der Pruefsumme exportieren
// pData: Zeiger auf Datenfeld
// len : Groesse des Datenfelds in Bytes
export unsigned char CalcCS(const unsigned char* pData, size_t len)
{
// Pruefsumme initialisieren
unsigned char checksum = pData[0];
// Alle Bytes des Datenfeldes exklusiv verodern
for (decltype(len) index = 1; index < len; index++)
checksum ^= pData[index];
// Pruefsumme zurueckgeben
return checksum;
}
// Ueberpruefen des Inhalt des Feldes auf Fehler
export bool CheckCS(const unsigned char* pData, size_t len, unsigned int cs)
{
// Pruefsumme berechnen
unsigned char checksum = pData[0];
for (decltype(len) index = 1; index < len; index++)
checksum ^= pData[index];
// Pruefsumme mit Vorgabe vergleichen und im Fehlerfall
// eine Meldung ausgeben
auto csOk = (cs == checksum);
if (!csOk)
std::cout << std::format("Pruefsummenfehler! Soll:{}, Ist:{}\n", cs, checksum);
// Ergebnis der Ueberpruefung zurueckgeben
return csOk;
}
// Anwendung Datei main.cpp
// ========================
#include <iostream>
// Pruefsummen-Modul importieren
import Checksum;
int main()
{
// Zu pruefende int-Daten
int data[]{ 10,20,30,40,50,60,70,80,90 };
// Pruefsumme berechnen
auto checksum = CalcCS(reinterpret_cast<unsigned char*>(data), sizeof(data));
// Pruefsumme testen
auto res = CheckCS(reinterpret_cast<unsigned char*>(data), sizeof(data), checksum);
if (res)
std::cout << "Alles ok!\n";
// Ein int-Datum modizieren
data[2] = 99;
// Nochmals Pruefsumme testen
res = CheckCS(reinterpret_cast<unsigned char*>(data), sizeof(data), checksum);
if (res)
std::cout << "Alles ok!\n";
}