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 affiancati ai file di intestazione. Un file di origine C++ può 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. È consigliabile usare i moduli nei nuovi progetti anziché nei file di intestazione il più possibile. Per progetti esistenti di dimensioni maggiori in fase di sviluppo attivo, provare a convertire le 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.

Abilitare i moduli nel compilatore Microsoft C++

A partire da Visual Studio 2022 versione 17.1, i moduli standard C++20 sono completamente implementati nel compilatore Microsoft C++.

Prima di essere specificato dallo standard C++20, Microsoft aveva il supporto sperimentale per i moduli. Il compilatore supporta anche l'importazione di moduli di libreria standard predefiniti, descritti di seguito.

A partire da Visual Studio 2022 versione 17.5, l'importazione della libreria standard come modulo è standardizzata e completamente implementata nel compilatore Microsoft C++. Questa sezione descrive il metodo sperimentale precedente, ancora supportato. Per informazioni sul nuovo modo standardizzato per importare la libreria standard usando i moduli, vedere Importare la libreria standard C++ usando i moduli.

È possibile usare la funzionalità moduli per creare moduli a partizione singola e importare i moduli della libreria standard forniti da Microsoft. Per abilitare il supporto per i moduli della libreria standard, compilare con /experimental:module e /std:c++latest. In un progetto di Visual Studio fare clic con il pulsante destro del mouse sul nodo del progetto in Esplora soluzioni e scegliere Proprietà. Impostare l'elenco a discesa Configurazione su Tutte le configurazioni, quindi scegliere Proprietà>di configurazione C/C++>Linguaggio>Abilita moduli C++ (sperimentale).

Un modulo e il codice che lo utilizza devono essere compilati con le stesse opzioni del compilatore.

Usare la libreria standard C++ come moduli (sperimentale)

Questa sezione descrive l'implementazione sperimentale, ancora supportata. Il nuovo modo standardizzato per usare la libreria standard C++ come moduli è descritto in Importare la libreria standard C++ usando i moduli.

Importando la libreria standard C++ come moduli invece di includerlo tramite i file di intestazione, è possibile velocizzare i tempi di compilazione a seconda delle dimensioni del progetto. La libreria sperimentale è suddivisa nei moduli denominati seguenti:

  • std.regex fornisce il contenuto dell'intestazione <regex>
  • std.filesystem fornisce il contenuto dell'intestazione <filesystem>
  • std.memory fornisce il contenuto dell'intestazione <memory>
  • std.threading fornisce il contenuto delle intestazioni <atomic>, <condition_variable>, <future>, <mutex>, <shared_mutex>e <thread>
  • std.core fornisce tutto il resto nella libreria standard C++

Per utilizzare questi moduli, aggiungere una dichiarazione di importazione all'inizio del file di codice sorgente. Ad esempio:

import std.core;
import std.regex;

Per utilizzare i moduli di Microsoft Standard Library, compilare il programma con le /EHsc opzioni e /MD .

Esempio

Nell'esempio seguente viene illustrata una definizione di modulo semplice in un file di origine denominato Example.ixx. L'estensione .ixx è necessaria per i file di interfaccia del modulo in Visual Studio. In questo esempio il file di interfaccia contiene sia la definizione della funzione che la dichiarazione. Tuttavia, è anche possibile inserire le definizioni in uno o più file di implementazione del modulo separati, come illustrato in un esempio successivo.

L'istruzione export module Example; indica che questo file è l'interfaccia primaria per un modulo denominato Example. Il export modificatore su 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 Example;
import std.core;

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.

Grammatica del modulo

module-name:
module-name-qualifier-seqoptare identifier

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

module-partition:
: module-name

module-declaration:
exportopt module-partitionoptattribute-specifier-seqmodule module-name ;

module-import-declaration:
exportopt opt opt import module-name attribute-specifier-seq ;
exportopt opt opt import module-partition attribute-specifier-seq ;
exportopt opt opt import header-name attribute-specifier-seq ;

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. Nella dichiarazione del modulo è export module presente un'unità di interfaccia 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ò import essere un altro modulo, ma non export può nomi. I file di implementazione possono avere qualsiasi estensione.

Moduli, spazi dei nomi e ricerca dipendente dagli argomenti

Le regole per gli spazi dei nomi nei moduli sono uguali a qualsiasi altro codice. Se viene esportata una dichiarazione all'interno di uno spazio dei nomi, viene esportato in modo implicito anche lo spazio dei nomi che include membri che non vengono esportati in modo esplicito in tale spazio dei nomi. 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 le risoluzioni di 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 di origine del modulo inserendo una #include direttiva prima della dichiarazione del modulo. Questi file vengono considerati nel frammento di modulo globale. Un modulo può visualizzare solo i nomi nel frammento di modulo globale presenti nelle intestazioni incluse in modo esplicito. Il frammento di modulo globale contiene solo i simboli utilizzati.

// MyModuleA.cpp

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

import std.core;
import MyModuleB;

//... rest of file

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

// MyProgram.h
import std.core;
#ifdef DEBUG_LOGGING
import std.filesystem;
#endif

File di intestazione importati

Alcune intestazioni sono sufficientemente autonome che possono essere portate usando la 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";

Vedi anche

module, import, export
Esercitazione su moduli denominati
Confrontare unità di intestazione, moduli e intestazioni precompilate