Moduli

Moduli

I moduli sono una delle cinque caratteristiche principali di C++20. I moduli supereranno le restrizioni dei file di intestazione. Promettono molto. Ad esempio, la separazione dei file di intestazione e di origine diventa obsoleta quanto il preprocessore. Alla fine, avremo anche tempi di compilazione più rapidi e un modo più semplice per creare pacchetti.

Spiegare i moduli dal punto di vista degli utenti è abbastanza facile, ma questo non vale per il punto di vista degli implementatori. Il mio piano per questo post è iniziare con un semplice esempio di moduli e aggiungere più funzionalità man mano che procediamo.

Un primo esempio

Prima di tutto, ecco il mio primo modulo di matematica.

// math.cppm

export module math;

export int add(int fir, int sec){
 return fir + sec;
} 

L'espressione export module math è la dichiarazione del modulo. Mettendo export prima della funzione aggiunge, add viene esportato e può, quindi, essere utilizzato da un consumatore del mio modulo.

// main.cpp

import math;

int main(){
 
 add(2000, 20);
 
}

import math importa il modulo math e rende visibili i nomi esportati in main.cpp. Questa è stata la parte facile. La sfida è iniziata quando ho compilato il programma.

File di dichiarazione del modulo

Ma prima, hai notato lo strano nome del modulo:math.cppm.

  • L'estensione cppm sta presumibilmente per cpp module Declaration ed è l'estensione suggerita per Clang.
  • cl.exe utilizza l'estensione ixx. La io dovrebbe in questo caso significare interfaccia.
  • Non conosco un'estensione GCC.

Compila la matematica del modulo

Per compilare il modulo è necessario utilizzare un compilatore Clang o cl.exe molto attuale. È anche possibile utilizzare gcc per compilare gli esempi di questo post, ma andrò in questo post con clang e cl.exe su Windows. Ecco maggiori dettagli sui miei compilatori:

  • clang++

  • cl.exe

Ecco esattamente il punto in cui è iniziato il divertimento:capire la riga di comando per clang++ e cl.exe.

clang++ -std=c++2a -fmodules-ts --precompile math.cppm -o math.pcm // 1
clang++ -std=c++2a -fmodules-ts -c math.pcm -o math.o // 2
clang++ -std=c++2a -fmodules-ts -fprebuilt-module-path=. math.o main.cpp -o math // 3


cl.exe /std:c++latest /experimental:module /TP /EHsc /MD /c math.cppm /module:interface /Fo: math.obj /module:output math.pcm // 1
cl.exe /std:c++latest /experimental:module /TP /EHsc /MD /c main.cpp /module:reference math.pcm /Fo: main.obj // 2
cl.exe math.obj main.obj // 3

  1. Crea un modulo precompilato math.pcm dalla dichiarazione del modulo math.cppm
  2. Crea l'unità di traduzione non di modulo math.o.
  3. Crea l'eseguibile math o math.exe. Per clang++ devo specificare il percorso del modulo.

Per ovvi motivi, non ti mostrerò l'output dell'esecuzione del programma. Lo farò se ho qualcosa da mostrare.

Dal punto di vista dell'implementatore, possiamo dividere la definizione del modulo in un'unità di interfaccia del modulo e un'unità di implementazione del modulo. Prima di venire in queste unità, vorrei fare un passo indietro e rispondere alla domanda:

Quali sono i vantaggi dei moduli?

  • Accelerazione del tempo di compilazione: Un modulo viene importato solo una volta e dovrebbe essere letteralmente gratuito. Confronta questo con le intestazioni M che sono incluse in N unità di traduzione. L'esplosione combinatoria significa che l'intestazione deve essere analizzata M*N volte.
  • Isolamento dalle macro del preprocessore :Se c'è un consenso nella comunità C++, è il seguente:dovremmo sbarazzarci delle macro del preprocessore. Come mai? L'uso di una macro è solo la sostituzione del testo escludendo qualsiasi semantica C++. Naturalmente, ciò ha molte conseguenze negative:ad esempio, può dipendere dalla sequenza in cui si includono le macro o le macro possono entrare in conflitto con macro o nomi già definiti nell'applicazione. Al contrario, non fa differenza in quale ordine importi i moduli.
  • Esprimi la struttura logica del tuo codice :I moduli consentono di esprimere quali nomi devono essere esportati o meno in modo esplicito. Puoi raggruppare alcuni moduli in un modulo più grande e fornirli al tuo cliente come un pacchetto logico.
  • Non c'è bisogno di file di intestazione: Non è necessario separare i file in un'interfaccia e una parte di implementazione. Ciò significa che i moduli sono solo la metà del numero di file di origine.
  • Sbarazzarsi di brutte soluzioni alternative: Siamo abituati a brutte soluzioni alternative come "mettere una protezione di inclusione attorno all'intestazione" o "scrivere macro con LONG_UPPERCASE_NAMES". Al contrario, nomi identici nei moduli non si scontrano.

Nel mio primo modulo di matematica, ho dichiarato e definito il modulo in un file math.cppm. Vorrei parlare delle nuove unità.

Unità di interfaccia del modulo e unità di implementazione del modulo

Innanzitutto, il nuovo modulo math1 è costituito da un'unità di interfaccia del modulo e da un'unità di implementazione del modulo.

Unità di interfaccia modulo

// math1.cppm

export module math1;

export int add(int fir, int sec);

  • L'unità di interfaccia del modulo contiene la dichiarazione del modulo di esportazione:export module math1.
  • Nomi come add possono essere esportati solo nell'unità di interfaccia del modulo.
  • I nomi che non vengono esportati non sono visibili all'esterno del modulo. Arriverò a questo punto nel prossimo post.
  • Un modulo può avere solo un'unità di interfaccia del modulo.

Unità di implementazione del modulo

// math1.cpp

module math1;

int add(int fir, int sec){
 return fir + sec;
}

  • L'unità di implementazione del modulo contiene dichiarazioni di moduli non esportabili:module math1;
  • Un modulo può avere più di un'unità di implementazione del modulo.

Programma principale

// main1.cpp

import math1;

int main(){
 
 add(2000, 20);
 
}

  • Dal punto di vista degli utenti, solo il nome del modulo è cambiato da math a math1.

La compilazione del modulo modularizzato è un po' più complicata.

Compila il modulo math1

clang++ -std=c++2a -fmodules-ts --precompile math1.cppm -o math1.pcm // 1
clang++ -std=c++2a -fmodules-ts -c math1.pcm -o math1.pcm.o // 2
clang++ -std=c++2a -fmodules-ts -c math1.cpp -fmodule-file=math1.pcm -o math1.o // 2
clang++ -std=c++2a -fmodules-ts -c main1.cpp -fmodule-file=math1.pcm -o main1.o // 3
clang++ math1.pcm main1.o math1.o -o math // 4

cl.exe /std:c++latest /experimental:module /TP /EHsc /MD /c math1.cppm /module:interface /Fo: math1.pcm.obj /module:output math1.pcm // 1
cl.exe /std:c++latest /experimental:module /TP /EHsc /MD /c math1.cpp /module:reference math1.pcm /Fo: math1.obj // 2
cl.exe /std:c++latest /experimental:module /TP /EHsc /MD /c main1.cpp /module:reference math1.pcm /Fo: main1.obj // 3
cl.exe math1.obj main1.obj math1.pcm.obj // 4

  1. Crea un modulo precompilato math1.pcm dalla dichiarazione del modulo math1.cppm
  2. Compila il modulo precompilato math1.pcm:math1.pcm.o. Compila il file sorgente math1.cpp:math1.o. cl.exe esegue questa operazione in un solo passaggio.
  3. Compila il programma principale:main1.o o main1.obj.
  4. Crea l'eseguibile math1 o math1.exe.

Cosa c'è dopo?

Come promesso, questa era solo un'introduzione ai moduli. Nel mio prossimo post, mi addentrerò di più nei dettagli. In particolare, voglio mostrare l'output del programma e quindi includere intestazioni standard come o moduli di importazione come std.core.