Condividi tramite


Panoramica dei moduli in C++

C++20 introduce i moduli. Un modulo è un set di file di codice sorgente compilati indipendentemente dai file di origine (o più precisamente, le unità di conversione) che le importano.

I moduli eliminano o riducono molti dei problemi associati all'uso dei file di intestazione. Spesso riducono i tempi di compilazione, a volte in modo significativo. Le macro, le direttive del preprocessore e i nomi non esterni dichiarati in un modulo non sono visibili all'esterno del modulo. Non hanno alcun effetto sulla compilazione dell'unità di traduzione che importa il modulo. È possibile importare moduli in qualsiasi ordine senza preoccupazioni per la ridefinizione delle macro. Le dichiarazioni nell'unità di conversione di importazione non partecipano alla risoluzione dell'overload o alla ricerca dei nomi nel modulo importato. Una volta compilato un modulo, i risultati vengono archiviati in un file binario che descrive tutti i tipi, le funzioni e i modelli esportati. Il compilatore può elaborare il file molto più velocemente di un file di intestazione. Inoltre, il compilatore può riutilizzarlo ogni posizione in cui il modulo viene importato in un progetto.

È possibile usare i moduli insieme ai file di intestazione. Un file di origine C++ può includere moduli import e anche #include file di intestazione. In alcuni casi, è possibile importare un file di intestazione come modulo, che è più veloce rispetto all'uso #include per elaborarlo con il preprocessore. Si consiglia di utilizzare i moduli quanto più possibile nei nuovi progetti anziché i file di intestazione. Per progetti di grandi dimensioni già esistenti e in sviluppo attivo, si consiglia di sperimentare con la conversione delle intestazioni legacy in moduli. Basare l'adozione su se si ottiene una riduzione significativa dei tempi di compilazione.

Per confrontare i moduli con altri modi per importare la libreria standard, vedere Confrontare le unità di intestazione, i moduli e le intestazioni precompilate.

A partire da Visual Studio 2022 versione 17.5, l'importazione della libreria standard come modulo è standardizzata e completamente implementata nel compilatore Microsoft C++. Per informazioni su come importare la libreria standard usando i moduli, vedere Importare la libreria standard C++ usando i moduli.

Moduli a partizione singola

Un modulo a partizione singola è un modulo costituito da un singolo file di origine. L'interfaccia e l'implementazione del modulo si trovano nello stesso file.

Nell'esempio seguente di modulo a partizione singola viene illustrata una definizione di modulo semplice in un file di origine denominato Example.ixx. L'estensione .ixx è l'estensione predefinita per i file di interfaccia del modulo in Visual Studio. Per usare un'estensione diversa, usare l'opzione /interface per compilarla come interfaccia del modulo. In questo esempio il file di interfaccia contiene sia la definizione della funzione che la dichiarazione. È anche possibile inserire le definizioni in uno o più file di implementazione del modulo separati, come illustrato in un esempio successivo, ma questo è un esempio di un modulo a partizione singola.

L'istruzione export module Example; indica che questo file è l'interfaccia primaria per un modulo denominato Example. Il export modificatore prima int f() indica che questa funzione è visibile quando un altro programma o modulo importa Example:

// Example.ixx
export module Example;

#define ANSWER 42

namespace Example_NS
{
   int f_internal()
   {
     return ANSWER;
   }

   export int f()
   {
     return f_internal();
   }
}

Il file MyProgram.cpp usa import per accedere al nome esportato da Example. Il nome Example_NS dello spazio dei nomi è visibile qui, ma non tutti i relativi membri perché non vengono esportati. Inoltre, la macro ANSWER non è visibile perché le macro non vengono esportate.

// MyProgram.cpp
import std;
import Example;

using namespace std;

int main()
{
   cout << "The result of f() is " << Example_NS::f() << endl; // 42
   // int i = Example_NS::f_internal(); // C2039
   // int j = ANSWER; //C2065
}

La import dichiarazione può essere visualizzata solo nell'ambito globale. Un modulo e il codice che lo utilizza devono essere compilati con le stesse opzioni del compilatore.

Grammatica del modulo

module-name:
module-name-qualifier-seq optareidentifier

module-name-qualifier-seq:
identifier .
module-name-qualifier-seq identifier .

module-partition:
: module-name

module-declaration:
export optaremodulemodule-namemodule-partitionoptareattribute-specifier-seqoptare;

module-import-declaration:
export optareimportmodule-nameattribute-specifier-seqoptare;
export optareimportmodule-partitionattribute-specifier-seqoptare;
export optareimportheader-nameattribute-specifier-seqoptare;

Implementazione di moduli

Un'interfaccia del modulo esporta il nome del modulo e tutti gli spazi dei nomi, i tipi, le funzioni e così via, che costituiscono l'interfaccia pubblica del modulo.
Un'implementazione del modulo definisce gli elementi esportati dal modulo.
Nel suo formato più semplice, un modulo può essere un singolo file che combina l'interfaccia del modulo e l'implementazione. È anche possibile inserire l'implementazione in uno o più file di implementazione del modulo separati, in modo analogo a come .h e .cpp i file lo eseguono.

Per i moduli di dimensioni maggiori, è possibile suddividere parti del modulo in moduli secondari denominati partizioni. Ogni partizione è costituita da un file di interfaccia del modulo che esporta il nome della partizione del modulo. Una partizione può avere anche uno o più file di implementazione della partizione. Il modulo nel suo complesso ha un'interfaccia del modulo principale, ovvero l'interfaccia pubblica del modulo. Può esportare le interfacce di partizione, se necessario.

Un modulo è costituito da una o più unità di modulo. Un'unità di modulo è un'unità di conversione (un file di origine) che contiene una dichiarazione di modulo. Esistono diversi tipi di unità modulo:

  • Un'unità di interfaccia del modulo esporta un nome di modulo o un nome di partizione del modulo. Un'unità di interfaccia del modulo ha export module nella sua dichiarazione del modulo.
  • Un'unità di implementazione del modulo non esporta un nome di modulo o un nome di partizione del modulo. Come suggerisce il nome, implementa un modulo.
  • Un'unità di interfaccia del modulo principale esporta il nome del modulo. In un modulo deve essere presente una sola unità di interfaccia del modulo principale.
  • Un'unità di interfaccia di partizione del modulo esporta un nome di partizione del modulo.
  • Un'unità di implementazione della partizione del modulo ha un nome di partizione del modulo nella dichiarazione del modulo, ma nessuna export parola chiave.

La export parola chiave viene usata solo nei file di interfaccia. Un file di implementazione può fungere da un altro modulo, ma non può nominare alcun nome. I file di implementazione possono avere qualsiasi estensione.

Moduli, spazi dei nomi e ricerca basata sugli argomenti

Le regole per i namespace nei moduli sono le stesse di qualsiasi altro tipo di codice. Se una dichiarazione all'interno di uno spazio dei nomi viene esportata, lo spazio dei nomi racchiudente (escluse le voci che non sono esportate esplicitamente in tale spazio) viene anch'esso esportato implicitamente. Se uno spazio dei nomi viene esportato in modo esplicito, vengono esportate tutte le dichiarazioni all'interno della definizione dello spazio dei nomi.

Quando il compilatore esegue la ricerca dipendente dall'argomento per la risoluzione dell'overload nell'unità di conversione di importazione, considera le funzioni dichiarate nella stessa unità di conversione (incluse le interfacce del modulo) come in cui viene definito il tipo degli argomenti della funzione.

Partizioni del modulo

Una partizione di modulo è simile a un modulo, ad eccezione di:

  • Condivide la proprietà di tutte le dichiarazioni nell'intero modulo.
  • Tutti i nomi esportati dai file di interfaccia di partizione vengono importati ed esportati dal file di interfaccia primaria.
  • Il nome di una partizione deve iniziare con il nome del modulo seguito da due punti (:).
  • Le dichiarazioni in una delle partizioni sono visibili all'interno dell'intero modulo.
  • Non sono necessarie precauzioni speciali per evitare errori ODR (One Definition Rule). È possibile dichiarare un nome (funzione, classe e così via) in una partizione e definirlo in un'altra.

Un file di implementazione della partizione inizia così ed è una partizione interna dal punto di vista degli standard C++:

module Example:part1;

Un file di interfaccia di partizione inizia come segue:

export module Example:part1;

Per accedere alle dichiarazioni in un'altra partizione, è necessario importarla. Ma può usare solo il nome della partizione, non il nome del modulo:

module Example:part2;
import :part1;

L'unità di interfaccia primaria deve importare ed esportare nuovamente tutti i file di partizione dell'interfaccia del modulo, come illustrato di seguito:

export import :part1;
export import :part2;

L'unità di interfaccia primaria può importare i file di implementazione della partizione, ma non può esportarli. Tali file non sono autorizzati a esportare nomi. Questa restrizione consente a un modulo di mantenere i dettagli di implementazione interni al modulo.

Moduli e file di intestazione

È possibile includere i file di intestazione in un file sorgente di un modulo inserendo la direttiva #include prima della dichiarazione del modulo. Questi file sono considerati appartenenti al frammento di modulo globale. Un modulo può accedere solo ai nomi del frammento globale del modulo che si trovano nelle intestazioni che include in modo esplicito. Il frammento di modulo globale contiene solo i simboli utilizzati.

// MyModuleA.cpp

#include "customlib.h"
#include "anotherlib.h"

import std;
import MyModuleB;

//... rest of file

È possibile usare un file di intestazione tradizionale per controllare quali moduli vengono importati:

// MyProgram.h
#ifdef C_RUNTIME_GLOBALS
import std.compat;
#else
import std;
#endif

File di intestazione importati

Alcune intestazioni sono sufficientemente autonome da poter essere importate usando il import parola chiave. La differenza principale tra un'intestazione importata e un modulo importato è che tutte le definizioni del preprocessore nell'intestazione sono visibili nel programma di importazione immediatamente dopo l'istruzione import .

import <vector>;
import "myheader.h";

Vedere anche

Importare la libreria standard C++ usando i moduli
module, import, export
Esercitazione sui moduli denominati
Confrontare unità di intestazione, moduli e intestazioni precompilate