zurück zum Artikel

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

Rainer Grimm

(Bild: SergioVas/Shutterstock)

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

In meinem letzten Artikel, "C++20: Modulunterstützung der großen drei Compiler [1]", 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]
  2. Ein einfaches math Modul [3]
  3. Module Interface Unit und Module Implementation Unit [4]
  4. Module strukturieren [5]
  5. Weitere offene Fragen zu Modulen [6]
  6. Private Module Fragment und Header Units [7]
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.

cl.exe /c /std:c++latest /interface /TP math.cppm​
cl.exe /c /std:c++latest /ifcOnly /interface /TP math.cppm​
cl.exe /c /std:c++latest /interface /TP math.cppm /ifcOutput mathematic.ifc​
cl.exe /std:c++latest client.cpp math.obj /reference math.inter​
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 [8]. In den folgenden Befehlszeilen verwende ich die Compiler-Optionen für das Modul und die ifc-Datei.

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 [9] 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:

// head.h

#include <iostream>

void hello();
// head.cpp

import "head.h";

void hello() {

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

}
// 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 [10] konnte ich mit dem Clang Compiler keine Header Units erzeugen.

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

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

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 [11])


URL dieses Artikels:
https://www.heise.de/-9340264

Links in diesem Artikel:
[1] https://www.heise.de/blog/C-20-Modulunterstuetzung-der-grossen-drei-Compiler-9329291.html
[2] https://heise.de/-4717856
[3] https://heise.de/-4722629
[4] https://heise.de/-4727382
[5] https://heise.de/-4770234
[6] https://heise.de/-4776210
[7] https://heise.de/-6182100
[8] https://clang.llvm.org/docs/StandardCPlusPlusModules.html
[9] https://heise-academy.de/schulungen/cplusplus20?wt_mc=intern.academy.ix.ws_cplusplus20.newsticker.link.link
[10] https://clang.llvm.org/docs/StandardCPlusPlusModules.html
[11] mailto:rme@ix.de