C++ Tutorial

 Module

Bisherige Vorgehensweise

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.

Modul-Definition

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:

Visual Studio

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.

CodeBlocks mit MinGW

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.

Einbinden Modul-Datei

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));
}

Globales Modul

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";
}