C++20: Weitere Details zur Modulunterstützung der großen drei Compiler

Im Vergleich zeigt sich, wie sich die großen drei Compiler GCC, Clang und der Microsoft-Compiler im Zusammenspiel mit Modulen schlagen.

In Pocket speichern vorlesen Druckansicht 5 Kommentare lesen

(Bild: SergioVas/Shutterstock)

Lesezeit: 5 Min.
Von
  • Rainer Grimm
Inhaltsverzeichnis

In meinem letzten Artikel, "C++20: Modulunterstützung der großen drei Compiler", habe ich ein einfaches Modul mit den Großen Drei kompiliert. Heute gehe ich tiefer und verwende den GCC, Clang und den Microsoft Compiler.

Modernes C++ – Rainer Grimm

Rainer Grimm ist seit vielen Jahren als Softwarearchitekt, Team- und Schulungsleiter tätig. Er schreibt gerne Artikel zu den Programmiersprachen C++, Python und Haskell, spricht aber auch gerne und häufig auf Fachkonferenzen. Auf seinem Blog Modernes C++ beschäftigt er sich intensiv mit seiner Leidenschaft C++.

Dieser Artikel ist ziemlich technisch und endet mit einer kuriosen Beobachtung. Darüber hinaus setzt er grundlegende Kenntnisse über Module voraus, das sich in den vorherigen Artikeln über Module findet:

  1. Die Vorteile von Modulen
  2. Ein einfaches math Modul
  3. Module Interface Unit und Module Implementation Unit
  4. Module strukturieren
  5. Weitere offene Fragen zu Modulen
  6. Private Module Fragment und Header Units
Compiler-Unterstützung von Modulen

Ich verwende Microsofts cl.exe 19.29.20133 für x64, den Clang 16.0.5 und den GCC 11.1.0 Compiler für meine Experimente.

Beginnen wir mit dem Microsoft Compiler.

Der Microsoft Visual Compiler bietet verschiedene Optionen für die Verwendung von Modulen an:

Außerdem gibt es ein paar allgemeine cl.exe Compiler-Optionen:

In den folgenden Befehlszeilen verwende ich verschiedene Compiler-Optionen für die ifc-Datei. Die ifc-Datei ist das Modul und enthält die Metadatenbeschreibung der Modulschnittstelle.

  • Verwende das Modul math.cppm, um die obj- und ifc-Datei zu erstellen.
cl.exe /c /std:c++latest /interface /TP math.cppm​
  • Verwende das Modul math.cppm, um nur die ifc-Datei zu erstellen.
cl.exe /c /std:c++latest /ifcOnly /interface /TP math.cppm​
  • Verwende das Modul math.cppm, um die obj-Datei math.obj und die ifc-Datei mathematic.ifc zu erstellen.
cl.exe /c /std:c++latest /interface /TP math.cppm /ifcOutput mathematic.ifc​
  • Erzeuge die ausführbare Datei client.exe und verwende dabei die ifc-Datei math.inter.
cl.exe /std:c++latest client.cpp math.obj /reference math.inter​
  • Erzeugt die ausführbare Datei client.exe und verwendet explizit die ifc-Datei math.inter, die sich im Verzeichnis ifcFiles befindet.
cl.exe /std:c++latest client.cpp math.obj /ifcSearchDir ifcFiles /reference math.inter

Nun kommt der Clang-Compiler zum Einsatz.

Der Clang-Compiler bietet verschiedene Optionen für die Erstellung von Modulen an:

Weitere Einzelheiten finden sich in der offiziellen Dokumentation zu Standard C++ Modules. In den folgenden Befehlszeilen verwende ich die Compiler-Optionen für das Modul und die ifc-Datei.

  • Verwende die Moduldeklarationsdatei math.cppm, um die pcm-Datei math.pcm zu erstellen.
clang++ -c -std=c++20 -fmodule-output math.cppm -o math.pcm​

Verwende das Modul mit der Erweiterung ixx (math.ixx), um die pcm-Datei math.pcm zu erstellen.

clang++ -std=c++20 --precompile -x c++-module math.ixx -o math.pcm​

Erstelle die pcm-Datei und verwende sie.

clang++ -std=c++20 -c math.pcm -o math.o​
clang++ -std=c++20 -fprebuilt-module-path=. math.o client.cpp -o client.exe​

Verwende die pcm-Datei other.pcm und kompiliere sie.

clang++ -std=c++20 -c client.cpp -fmodule-file=math=other.pcm -o client.o

Damit komme ich zum GCC-Compiler.

iX-Workshop mit Rainer Grimm: C++20 Deep Dive

Die mit C++20 eingeführten Concepts haben zusammen mit der Ranges-Bibliothek, Modulen und Coroutinen das Erstellen von modernen C++- Anwendungen neu definiert. Vom 7. bis zum 9. November 2023 bringt Sie Rainer Grimm in seinem Intensiv-Workshop C++20: die neuen Konzepte umfassend erklärt auf Stand und geht auf die vielen nützlichen Funktionen ein, die C++20 mitbringt.

Die folgende Tabelle zeigt die wenigen GCC-Optionen:

Viele Optionen der Großen Drei addressieren Header Units.

Header Units sind eine binäre Darstellung von Header-Dateien und stellen einen Übergang von Headern zu Modulen dar. Man muss die #include-Direktive durch die neue Import-Anweisung ersetzen und ein Semikolon (;) hinzufügen.

#include <vector>      => import <vector>;
#include "myHeader.h"  => import "myHeader.h";

Weitere Informationen über Header Units finden sich in meinem vorherigen Artikel "Private Module Fragment and Header Units". In den folgenden Zeilen spiele ich mit Header Units und verwende die folgenden Dateien:

  • Die Header-Datei head.h
// head.h

#include <iostream>

void hello();
  • Die Quelldatei head.cpp, die die Header Unit importiert
// head.cpp

import "head.h";

void hello() {

    std::cout << '\n';
    std::cout << "Hello World: header units\n";
    std::cout << '\n';

}
  • Das Hauptprogramm helloWorld3.cpp, das die Header Unit importiert
// helloWorld3.cpp

import "head.h";

int main() {

    hello();

}

Ich werde eine Header Unit aus der Header-Datei head.h für den Microsoft Visual Compiler und den GCC Compiler erstellen. Im Gegensatz zur offiziellen Dokumentation Standard C++ Modules konnte ich mit dem Clang Compiler keine Header Units erzeugen.

  • Microsoft Visual Compiler

Dies sind die notwendigen Schritte, um Header Units zu verwenden.

cl.exe /std:c++latest /EHsc /exportHeader head.h
cl.exe /c /std:c++latest /EHsc /headerUnit head.h=head.h.ifc head.cpp
cl.exe /std:c++latest /EHsc /headerUnit head.h=head.h.ifc helloWorld3.cpp head.obj
  • Das Flag /exportHeader in Zeile 1 bewirkt, dass die ifc-Datei head.h.ifc aus der Header-Datei head.h erstellt wird.
  • Die Implementierungsdatei head.cpp (Zeile 2) und die Client-Datei helloWordl3.cpp (Zeile 3) verwenden die Header-Unit. Das Flag /headerUnit head.h=head.h.ifc importiert die Header-Unit und teilt dem Compiler den Namen der ifc-Datei für den angegebenen Header mit.
  • GCC-Compiler

Die Erstellung und Verwendung des Moduls besteht aus drei Schritten.

g++ -fmodules-ts -fmodule-header head.h -std=c++20
g++ -fmodules-ts -c  -std=c++20 head.cpp 
g++ -fmodules-ts  -std=c++20 head.o helloWorld3.cpp -o helloWorld3
  • (1) erstellt das Modul head.gcm. Das Flag -fmodule-header gibt an, dass aus head.h eine Header Unit erstellt werden soll.
  • Die folgende Zeile erstellt die Objektdatei head.o.
  • Schließlich wird in Zeile 3 die ausführbare Datei erstellt, die implizit das Modul head.gcm verwendet.

Wie versprochen, folgt nun die kuriose Beobachtung.

Bei Modulen muss man zwischen Erreichbarkeit und Sichtbarkeit unterscheiden. Wenn ein Modul eine Entität exportiert, kann ein importierender Client sie sehen und nutzen. Nicht exportierte Entitäten sind nicht sichtbar, können aber erreichbar sein.

// bar.cppm

module;

#include <iostream>

export module bar;

struct Foo {
    void writeName() {
        std::cout << "\nFoo\n";
    }

};

export struct Bar {
    Foo getFoo() {
        return Foo{};
    }
};

Das Modul bar exportiert die Klasse Bar. Bar ist sichtbar und erreichbar. Foo hingegen ist nicht sichtbar.

// bar.cpp

#include <utility>

import bar;

int main() {

    Bar b;
    // Foo f;                   // (1)          
    auto f = b.getFoo();   
    f.writeName();              // (2)

    using FooAlias = 
      decltype(std::declval<Bar>().getFoo());   // (3)
    FooAlias f2;                // (4)
    f2.writeName();             // (5)

}

Die Klasse Foo wird nicht exportiert und ist daher nicht sichtbar. Ihre Verwendung in (1) würde einen Linker-Fehler verursachen. Im Gegenteil, Foo ist erreichbar, weil die Mitgliedsfunktion getFoo (in bar.cppm) sie zurückgibt. Folglich kann die Funktion writeName (2) aufgerufen werden. Außerdem kann ich einen Datentyp-Alias zu Foo erstellen (3) und ihn verwenden, um Foo zu instanziieren (4) und writeName (5) aufzurufen. Der Ausdruck std::declval<Bar>().getFoo() (3) gibt das Objekt zurück, das ein Aufruf Bar.getFoo() zurückgeben würde. Schließlich gibt decltype den Datentyp dieses hypothetischen Objekts zurück.

In meinem nächsten Artikel werde ich tiefer in die Ranges-Bibliothek in C++20 eintauchen.

(rme)