Condividi tramite


Esercitazione: Importare la libreria standard C++ usando moduli dalla riga di comando

Informazioni su come importare la libreria standard C++ usando i moduli della libreria C++. Ciò comporta una compilazione più rapida ed è più affidabile rispetto all'uso di file di intestazione o unità di intestazione o intestazioni precompilate (PCH).

In questa esercitazione vengono fornite informazioni su:

  • Come importare la libreria standard come modulo dalla riga di comando.
  • Vantaggi delle prestazioni e dell'usabilità dei moduli.
  • I due moduli della libreria standard std e std.compat e la differenza tra di essi.

Prerequisiti

Questa esercitazione richiede Visual Studio 2022 17.5 o versione successiva.

Introduzione ai moduli di libreria standard

La semantica dei file di intestazione può cambiare a seconda delle definizioni di macro, dell'ordine in cui li includi e rallentano la compilazione. I moduli risolvono questi problemi.

È ora possibile importare la libreria standard come modulo anziché come tangente di file di intestazione. Questo è molto più veloce e più affidabile rispetto all'inclusione di file di intestazione o unità di intestazione o intestazioni precompilate (PCH).

La libreria standard C++23 introduce due moduli denominati: std e std.compat:

  • std esporta le dichiarazioni e i nomi definiti nello spazio dei nomi della libreria standard C++ std, ad esempio std::vector. Esporta anche il contenuto delle intestazioni wrapper C, ad esempio <cstdio> e <cstdlib>, che forniscono funzioni come std::printf(). Le funzioni C definite nello spazio dei nomi globale, ad esempio ::printf(), non vengono esportate. Ciò migliora la situazione in cui l'inclusione di un'intestazione wrapper C come <cstdio>eo comporta anche l'inclusione di file di intestazione C come stdio.h, che introducono le versioni del namespace globale di C. Questo non è un problema se si importa std.
  • std.compat esporta tutti gli elementi in std e aggiunge gli spazi dei nomi globali del runtime C, inclusi ::printf, ::fopen, ::size_t, ::strlene così via. Il modulo std.compat semplifica l'uso delle codebase che fanno riferimento a molte funzioni/tipi di runtime C nello spazio dei nomi globale.

Il compilatore importa l'intera libreria standard quando si usa import std; o import std.compat; ed esegue operazioni più veloci rispetto all'importazione di un singolo file di intestazione. È più veloce inserire l'intera libreria standard con import std; (o import std.compat) rispetto a #include <vector>, ad esempio.

Poiché i moduli denominati non espongono macro, macro come assert, errno, offsetof, va_arge altre non sono disponibili quando si importano std o std.compat. Per considerazioni sui moduli denominati della libreria standard, vedere per le soluzioni alternative.

Informazioni sui moduli C++

I file di intestazione sono il modo in cui le dichiarazioni e le definizioni sono state condivise tra i file di origine in C++. Prima dei moduli della libreria standard, è necessario includere la parte della libreria standard necessaria con una direttiva come #include <vector>. I file di intestazione sono fragili e difficili da comporre perché la semantica può cambiare a seconda dell'ordine in cui sono incluse o se determinate macro sono definite. Rallentano anche la compilazione perché vengono rielaborati da ogni file di origine che li include.

C++20 introduce un'alternativa moderna denominata moduli . In C++23 è stato possibile capitalizzare il supporto del modulo per introdurre moduli denominati per rappresentare la libreria standard.

Come i file di intestazione, i moduli consentono di condividere dichiarazioni e definizioni tra i file di origine. A differenza dei file di intestazione, tuttavia, i moduli non sono fragili e sono più facili da comporre perché la semantica non cambia a causa delle definizioni di macro o dell'ordine in cui vengono importate. Il compilatore può elaborare i moduli molto più velocemente di quanto possa elaborare #include file e usa meno memoria in fase di compilazione. I moduli denominati non espongono definizioni di macro o dettagli di implementazione privata.

Questo articolo illustra il nuovo e migliore modo per utilizzare la libreria standard. Per altre informazioni sui modi alternativi per utilizzare la libreria standard, vedere Confrontare unità di intestazione, moduli e intestazioni precompilate.

Importare la libreria standard con std

Negli esempi seguenti viene illustrato come utilizzare la libreria standard come modulo usando il compilatore della riga di comando. Per informazioni su come eseguire questa operazione nell'IDE di Visual Studio, vedere Build ISO C++23 Standard Library Modules.

L'istruzione import std; o import std.compat; importa la libreria standard nell'applicazione. Prima di tutto, è necessario compilare la libreria standard denominata modules in formato binario. I passaggi seguenti illustrano come.

Esempio: Come costruire e importare std

  1. Aprire un prompt dei comandi degli strumenti nativi x86 per Visual Studio: dal menu Start di Windows, digitare nativo x86 e il prompt dovrebbe essere visualizzato nell'elenco delle app. Assicurarsi che il prompt sia per Visual Studio 2022 versione 17.5 o successiva. Se si usa la versione errata del prompt, vengono visualizzati errori. Gli esempi usati in questa esercitazione sono relativi alla shell CMD.

  2. Creare una directory, ad esempio %USERPROFILE%\source\repos\STLModules, e impostarla come directory corrente. Se si sceglie una directory a cui non si ha accesso in scrittura, si otterranno errori durante la compilazione.

  3. Compilare il modulo denominato std con il comando seguente:

    cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx"
    

    Se si verificano errori, assicurarsi di usare la versione corretta del prompt dei comandi.

    Compilare il modulo denominato std usando le stesse impostazioni del compilatore che si intende usare con il codice che importa il modulo compilato. Se si dispone di una soluzione multiprogetto, è possibile compilare la libreria standard denominata module una sola volta e quindi farvi riferimento da tutti i progetti usando l'opzione del compilatore /reference.

    Usando il comando del compilatore precedente, il compilatore restituisce due file:

    • std.ifc è la rappresentazione binaria compilata dell'interfaccia del modulo denominata consultata dal compilatore per elaborare l'istruzione import std;. Si tratta di un artefatto solo in fase di compilazione. Non viene fornito con la tua applicazione.
    • std.obj contiene l'implementazione del modulo denominato. Aggiungere std.obj alla riga di comando quando si compila l'app di esempio per collegare in modo statico le funzionalità usate dalla libreria standard all'applicazione.

    Le principali opzioni della riga di comando in questo esempio sono:

    Interruttore Significato
    /std:c++:latest Usare la versione più recente dello standard e della libreria del linguaggio C++. Anche se il supporto dei moduli è disponibile in /std:c++20, è necessaria la libreria standard più recente per ottenere il supporto per i moduli denominati della libreria standard.
    /EHsc Usare la gestione delle eccezioni C++, ad eccezione delle funzioni contrassegnate extern "C".
    /W4 L'uso di /W4 è in genere consigliato, soprattutto per i nuovi progetti perché abilita tutti gli avvisi di livello 1, livello 2, livello 3 e 4 (informativo), che consentono di rilevare i potenziali problemi in anticipo. Fornisce essenzialmente avvisi simili a lint che consentono di garantire il minor numero possibile di errori di codice difficili da trovare.
    /c Eseguire la compilazione senza collegamento, perché a questo punto si sta creando solo l'interfaccia binaria denominata module.

    È possibile controllare il nome del file dell'oggetto e il nome file dell'interfaccia del modulo denominato con le opzioni seguenti:

    • /Fo imposta il nome del file oggetto. Ad esempio, /Fo:"somethingelse". Per impostazione predefinita, il compilatore usa lo stesso nome per il file oggetto del file di origine del modulo (.ixx) che si sta compilando. Nell'esempio, il nome del file dell'oggetto è std.obj per impostazione predefinita perché stiamo compilando il file del modulo std.ixx.
    • /ifcOutput imposta il nome del file di interfaccia del modulo denominato (.ifc). Ad esempio, /ifcOutput "somethingelse.ifc". Per impostazione predefinita, il compilatore usa lo stesso nome per il file di interfaccia del modulo (.ifc) come file di origine del modulo (.ixx) che si sta compilando. Nell'esempio, il file ifc generato è std.ifc per impostazione predefinita perché stiamo compilando il file del modulo std.ixx.
  4. Importare la raccolta std creata creando prima un file denominato importExample.cpp con il contenuto seguente:

    // requires /std:c++latest
    
    import std;
    
    int main()
    {
        std::cout << "Import the STL library for best performance\n";
        std::vector<int> v{5, 5, 5};
        for (const auto& e : v)
        {
            std::cout << e;
        }
    }
    

    Nel codice precedente import std; sostituisce #include <vector> e #include <iostream>. L'istruzione import std; rende disponibile tutta la libreria standard con un'unica istruzione. L'importazione dell'intera libreria standard è spesso molto più veloce rispetto all'elaborazione di un singolo file di intestazione della libreria standard, ad esempio #include <vector>.

  5. Compilate l'esempio utilizzando il comando seguente nella stessa directory del passaggio precedente:

    cl /c /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp
    link importExample.obj std.obj
    

    Non è necessario specificare /reference "std=std.ifc" nella riga di comando in questo esempio perché il compilatore cerca automaticamente il file .ifc corrispondente al nome del modulo specificato dall'istruzione import. Quando il compilatore rileva import std; può trovare std.ifc se si trova nella stessa directory del codice sorgente. Se il file .ifc si trova in una directory diversa dal codice sorgente, usare l'opzione del compilatore /reference per farvi riferimento.

    In questo esempio, la compilazione del codice sorgente e il collegamento dell'implementazione del modulo nell'applicazione sono passaggi separati. Non devono essere così. È possibile usare cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj per compilare e collegare in un unico passaggio. Tuttavia, può essere utile compilare e collegare separatamente perché è sufficiente compilare la libreria standard denominata module una sola volta, quindi è possibile farvi riferimento dal progetto o da più progetti, nel passaggio di collegamento della compilazione.

    Se si compila un singolo progetto, è possibile combinare i passaggi per compilare il modulo denominato std della libreria standard e il passaggio della compilazione dell'applicazione aggiungendoli alla riga di comando con "%VCToolsInstallDir%\modules\std.ixx". Inserirlo prima di qualsiasi file .cpp che utilizza il modulo std.

    Per impostazione predefinita, il nome dell'eseguibile di output viene ricavato dal primo file di input. Usare l'opzione del compilatore /Fe per specificare il nome del file eseguibile desiderato. Questa esercitazione illustra la compilazione del modulo denominato std come passaggio separato perché è sufficiente compilare la libreria standard denominata module una sola volta e quindi farvi riferimento dal progetto o da più progetti. Può essere conveniente costruire tutto insieme, come mostrato dalla riga di comando seguente:

    cl /FeimportExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" importExample.cpp
    

    Data la riga di comando precedente, il compilatore genera un eseguibile denominato importExample.exe. Quando viene eseguito, genera l'output seguente:

    Import the STL library for best performance
    555
    

Importare la libreria standard e le funzioni C globali con std.compat

La libreria standard C++ include la libreria standard ISO C. Il modulo std.compat fornisce tutte le funzionalità del modulo di std, ad esempio std::vector, std::cout, std::printf, std::scanfe così via. Ma fornisce anche le versioni dello spazio dei nomi globali di queste funzioni, ad esempio ::printf, ::scanf, ::fopen, ::size_te così via.

Il modulo denominato std.compat è un livello di compatibilità fornito per semplificare la migrazione del codice esistente che fa riferimento alle funzioni di runtime C nello spazio dei nomi globale. Per evitare di aggiungere nomi allo spazio dei nomi globale, usare import std;. Se è necessario semplificare la migrazione di una codebase che usa molte funzioni di runtime C non qualificate (spazio dei nomi globale), usare import std.compat;. Per evitare di dover qualificare tutti i nomi globali con std::, questo fornisce i nomi di runtime C dello spazio dei nomi globali. Se non si dispone di codice esistente che usa le funzioni di runtime C dello spazio dei nomi globale, non è necessario usare import std.compat;. Se nel codice si chiamano solo alcune funzioni di runtime C, potrebbe essere preferibile usare import std; e qualificare i pochi nomi di runtime C necessari nello spazio dei nomi globale con std::. Ad esempio, std::printf(). Se viene visualizzato un errore simile a error C3861: 'printf': identifier not found quando si tenta di compilare il codice, provare a usare import std.compat; per importare le funzioni di runtime C dello spazio dei nomi globale.

Esempio: Come costruire e importare std.compat

Prima di poter usare import std.compat; è necessario compilare il file di interfaccia del modulo presente nel modulo del codice sorgente in std.compat.ixx. Visual Studio fornisce il codice sorgente per il modulo in modo che sia possibile compilare il modulo usando le impostazioni del compilatore corrispondenti al progetto. I passaggi sono simili a per la compilazione del modulo denominato std. Il modulo denominato std viene creato per primo perché std.compat dipende da esso:

  1. Aprire un prompt dei comandi degli strumenti nativi per Visual Studio: dal menu Start di Windows, digitare x86 nativo e il prompt verrà visualizzato nell'elenco delle app. Assicurarsi che il prompt sia per Visual Studio 2022 versione 17.5 o successiva. Se si usa la versione errata del prompt, si otterranno errori del compilatore.

  2. Creare una directory per provare questo esempio, ad esempio %USERPROFILE%\source\repos\STLModulese impostarla come directory corrente. Se si sceglie una directory a cui non si ha accesso in scrittura, verranno visualizzati errori.

  3. Compilare i moduli denominati std e std.compat con il comando seguente:

    cl /std:c++latest /EHsc /nologo /W4 /c "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx"
    

    È necessario compilare std e std.compat usando le stesse impostazioni del compilatore che si intende usare con il codice che li importerà. Se si dispone di una soluzione multiprogetto, è possibile compilarli una sola volta e quindi farvi riferimento da tutti i progetti usando l'opzione del compilatore /reference.

    Se si verificano errori, assicurarsi di usare la versione corretta del prompt dei comandi.

    Il compilatore restituisce quattro file dai due passaggi precedenti:

    • std.ifc è l'interfaccia binaria denominata module compilata consultata dal compilatore per elaborare l'istruzione import std;. Il compilatore consulta anche std.ifc per elaborare import std.compat; perché std.compat si basa su std. Si tratta di un artefatto visibile solo durante la fase di compilazione. Non viene fornito con l'applicazione.
    • std.obj contiene l'implementazione della libreria standard.
    • std.compat.ifc è l'interfaccia binaria denominata module compilata consultata dal compilatore per elaborare l'istruzione import std.compat;. Questo è un elemento disponibile solo in fase di compilazione. Non viene spedito con l'applicazione.
    • std.compat.obj contiene l'implementazione. Tuttavia, la maggior parte dell'implementazione viene fornita da std.obj. Aggiungere std.obj alla riga di comando quando si compila l'app di esempio per collegare in modo statico le funzionalità usate dalla libreria standard all'applicazione.

    È possibile controllare il nome del file dell'oggetto e il nome file dell'interfaccia del modulo denominato con le opzioni seguenti:

    • /Fo imposta il nome del file oggetto. Ad esempio, /Fo:"somethingelse". Per impostazione predefinita, il compilatore usa lo stesso nome per il file oggetto del file di origine del modulo (.ixx) che si sta compilando. Nell'esempio i nomi dei file oggetto vengono std.obj e std.compat.obj per impostazione predefinita perché vengono compilati i file del modulo std.ixx e std.compat.obj.
    • /ifcOutput imposta il nome del file di interfaccia del modulo denominato (.ifc). Ad esempio, /ifcOutput "somethingelse.ifc". Per impostazione predefinita, il compilatore usa lo stesso nome per il file di interfaccia del modulo (.ifc) come file di origine del modulo (.ixx) che si sta compilando. Nell'esempio i file ifc generati vengono std.ifc e std.compat.ifc per impostazione predefinita perché vengono compilati i file del modulo std.ixx e std.compat.ixx.
  4. Importare la raccolta std.compat creando prima di tutto un file denominato stdCompatExample.cpp con il contenuto seguente:

    import std.compat;
    
    int main()
    {
        printf("Import std.compat to get global names like printf()\n");
    
        std::vector<int> v{5, 5, 5};
        for (const auto& e : v)
        {
            printf("%i", e);
        }
    }
    

    Nel codice precedente import std.compat; sostituisce #include <cstdio> e #include <vector>. L'istruzione import std.compat; rende disponibili le funzioni di runtime standard e C con un'unica istruzione. L'importazione di questo modulo denominato, che include la libreria standard C++ e le funzioni dello spazio dei nomi globale della libreria di runtime C, è più veloce rispetto all'elaborazione di un singolo #include come #include <vector>.

  5. Compilare l'esempio usando il comando seguente:

    cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp
    link stdCompatExample.obj std.obj std.compat.obj
    

    Non è stato necessario specificare std.compat.ifc nella riga di comando perché il compilatore cerca automaticamente il file .ifc che corrisponde al nome del modulo in un'istruzione import. Quando il compilatore rileva import std.compat; trova std.compat.ifc poiché lo abbiamo inserito nella stessa directory del codice sorgente, sollevandoci dalla necessità di specificarlo nella riga di comando. Se il file .ifc si trova in una directory diversa da quella del codice sorgente o ha un nome diverso, usare l'opzione del compilatore /reference per farvi riferimento.

    Quando si importa std.compat, è necessario collegarsi sia a std.compat che a std.obj perché std.compat usa il codice in std.obj.

    Se si sta costruendo un singolo progetto, è possibile combinare i passaggi per compilare i moduli della libreria standard nominati std e std.compat aggiungendo "%VCToolsInstallDir%\modules\std.ixx" e "%VCToolsInstallDir%\modules\std.compat.ixx" nell'ordine indicato alla riga di comando. Questa esercitazione illustra la compilazione dei moduli della libreria standard come passaggio separato perché è sufficiente compilare la libreria standard denominata modules una sola volta e quindi farvi riferimento dal progetto o da più progetti. Tuttavia, se è utile costruirli tutti contemporaneamente, assicurarsi di inserirli prima di qualsiasi file .cpp che li utilizza e specificare /Fe per denominare il exe costruito, come illustrato in questo esempio di seguito.

    cl /c /FestdCompatExample /std:c++latest /EHsc /nologo /W4 "%VCToolsInstallDir%\modules\std.ixx" "%VCToolsInstallDir%\modules\std.compat.ixx" stdCompatExample.cpp
    link stdCompatExample.obj std.obj std.compat.obj
    

    In questo esempio, la compilazione del codice sorgente e il collegamento dell'implementazione del modulo nell'applicazione sono passaggi separati. Non devono esserlo. È possibile usare cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.obj per compilare e collegare in un unico passaggio. Tuttavia, può essere utile compilare e collegare separatamente perché è sufficiente compilare la libreria standard denominata modules una sola volta, quindi è possibile farvi riferimento dal progetto o da più progetti nel passaggio di collegamento della compilazione.

    Il comando del compilatore precedente genera un eseguibile denominato stdCompatExample.exe. Quando viene eseguito, genera l'output seguente:

    Import std.compat to get global names like printf()
    555
    

Considerazioni sui moduli nominati della libreria standard

Il versionamento per i moduli denominati è lo stesso che per le intestazioni. I file di modulo denominati .ixx vengono installati insieme alle intestazioni, ad esempio: "%VCToolsInstallDir%\modules\std.ixx, che corrisponde a C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixx nella versione degli strumenti usati al momento della scrittura. Selezionare la versione del modulo denominato nello stesso modo in cui si sceglie la versione del file di intestazione da usare, ovvero dalla directory da cui si fa riferimento.

Non mescolare le unità di header e i moduli denominati. Ad esempio, non import <vector>; e import std; nello stesso file.

Non mescolare l'importazione di file di intestazione della libreria standard C++ con i moduli denominati std o std.compat. Ad esempio, non #include <vector> e import std; nello stesso file. Tuttavia, è possibile includere intestazioni C e importare moduli denominati nello stesso file. Ad esempio, è possibile import std; e #include <math.h> nello stesso file. Non includere la versione della libreria standard C++ <cmath>.

Non è necessario difendersi più volte dall'importazione di un modulo. Ciò significa che non sono necessarie le guardie per l'intestazione in stile #ifndef nei moduli. Il compilatore sa quando è già stato importato un modulo denominato e ignora i tentativi duplicati a tale scopo.

Se è necessario usare la macro assert(), #include <assert.h>.

Se è necessario utilizzare la macro errno, #include <errno.h>. Poiché i moduli denominati non espongono macro, questa è la soluzione alternativa se è necessario verificare la presenza di errori da <math.h>, ad esempio.

Le macro come NAN, INFINITYe INT_MIN sono definite da <limits.h>, che è possibile includere. Tuttavia, se si import std; è possibile usare numeric_limits<double>::quiet_NaN() e numeric_limits<double>::infinity() anziché NAN e INFINITYe std::numeric_limits<int>::min() anziché INT_MIN.

Sommario

In questa esercitazione è stata importata la libreria standard usando i moduli. Successivamente, scopri come creare e importare moduli personalizzati nel tutorial sui moduli nominati di in C++.

Vedere anche

Confrontare unità di intestazione, moduli e intestazioni precompilate
Panoramica dei moduli in C++
Presentazione dei moduli C++ in Visual Studio
Spostamento di un progetto in C++ denominato Modules