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 std della libreria standard e std.compat la differenza tra di essi.

Prerequisiti

Questa esercitazione richiede Visual Studio 2022 17.5 o versione successiva.

Introduzione ai moduli di libreria standard

I file di intestazione soffrono di semantica che possono cambiare a seconda delle definizioni di macro, dell'ordine in cui sono inclusi 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 stddella libreria standard C++, ad esempio std::vector. Esporta anche il contenuto delle intestazioni wrapper C, <cstdio> ad esempio 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>include anche i file di intestazione C come stdio.h, che portano le versioni dello spazio dei nomi globale C. Questo non è un problema se si importa std.
  • std.compatesporta tutti gli elementi in std e aggiunge gli spazi dei nomi globali del runtime C, ad ::printfesempio , ::fopen::size_t, ::strlen, e così via. Il std.compat modulo 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; e lo esegue più velocemente 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, errnooffsetof, va_arg, e altre non sono disponibili quando si importa std o std.compat. Per le soluzioni alternative, vedere Considerazioni sulla libreria Standard denominata module.

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 i #include file e usa meno memoria in fase di compilazione. I moduli denominati non espongono definizioni di macro o dettagli di implementazione privata.

Per informazioni approfondite sui moduli, vedere Panoramica dei moduli in C++ Che illustra anche l'uso della libreria standard C++ come moduli, ma usa un modo precedente e sperimentale per farlo.

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 le unità di intestazione, i moduli e le 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 Compilare moduli della libreria standard ISO C++23.

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 compilare e importare std

  1. Aprire un prompt dei comandi di Strumenti nativi x86 per Visual Studio: dal menu Start di Windows digitare x86 nativo 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, si otterranno errori. Gli esempi usati in questa esercitazione sono relativi alla shell CMD.

  2. Creare una directory, ad esempio %USERPROFILE%\source\repos\STLModulese 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 std modulo denominato 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 std modulo denominato 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 /reference compilatore.

    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 l'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 opzioni della riga di comando della chiave in questo esempio sono:

    Commutatore 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 da 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é viene compilato il file std.ixxdel modulo .
    • /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) del file di origine del modulo (.ixx) che si sta compilando. Nell'esempio il file generato ifc è std.ifc per impostazione predefinita perché viene compilato il file std.ixxdel modulo .
  4. Importare la std libreria 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 import std; precedente 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, #include <vector>ad esempio .

  5. Compilare l'esempio usando 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 di questo esempio perché il compilatore cerca automaticamente il .ifc file corrispondente al nome del modulo specificato dall'istruzione import . Quando viene rilevato dal compilatore import std; , può trovare std.ifc se si trova nella stessa directory del codice sorgente. Se il .ifc file si trova in una directory diversa dal codice sorgente, usare l'opzione del /reference compilatore 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. È 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 della compilazione della std libreria standard denominata module e il passaggio della compilazione dell'applicazione aggiungendo "%VCToolsInstallDir%\modules\std.ixx" alla riga di comando. Inserirlo prima di tutti i .cpp file che utilizzano il std modulo.

    Per impostazione predefinita, il nome dell'eseguibile di output viene ricavato dal primo file di input. Usare l'opzione del /Fe compilatore per specificare il nome del file eseguibile desiderato. Questa esercitazione illustra la compilazione del std modulo denominato come passaggio separato perché è sufficiente compilare la libreria standard denominata module una sola volta e quindi farvi riferimento dal progetto o da più progetti. Ma può essere utile compilare tutto insieme, come illustrato 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 std.compat modulo fornisce tutte le funzionalità del std modulo, ad esempio std::vector, std::cout, std::printfstd::scanf, e così via. Ma fornisce anche le versioni dello spazio dei nomi globali di queste funzioni, ad ::printfesempio , ::scanf::fopen, ::size_t, e così via.

Il std.compat modulo denominato è 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;. In questo modo vengono forniti i nomi di runtime C dello spazio dei nomi globali in modo che non sia necessario qualificare tutti i nomi globali con std::. 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 dello spazio dei nomi globali necessari con std::. Ad esempio: std::printf(). Se viene visualizzato un errore simile error C3861: 'printf': identifier not found a 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 compilare e importare std.compat

Prima di poter usare import std.compat; è necessario compilare il file di interfaccia del modulo disponibile 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 std modulo denominato. Il std modulo denominato 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 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, si otterranno errori del compilatore.

  2. Creare una directory per provare questo esempio, ad esempio %USERPROFILE%\source\repos\STLModules, e 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 usare 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 /reference compilatore.

    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 std.ifc anche per elaborare import std.compat; perché std.compat si basa su std. Si tratta di un artefatto solo in 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; . Si tratta di un artefatto solo in fase di compilazione. Non viene fornito 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 di oggetto sono std.obj e std.compat.obj , per impostazione predefinita, perché vengono compilati i file std.ixx di modulo 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) del file di origine del modulo (.ixx) che si sta compilando. Nell'esempio i file generati ifc sono std.ifc e std.compat.ifc , per impostazione predefinita, perché vengono compilati i file std.ixx del modulo e std.compat.ixx.
  4. Importare la std.compat libreria 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 import std.compat; precedente sostituisce #include <cstdio> e #include <vector>. L'istruzione import std.compat; rende disponibili le funzioni della libreria standard e del runtime 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 .ifc file che corrisponde al nome del modulo in un'istruzione import . Quando il compilatore import std.compat; rileva che viene trovato std.compat.ifc perché lo si inserisce nella stessa directory del codice sorgente, che si basa sulla necessità di specificarlo nella riga di comando. Se il .ifc file si trova in una directory diversa rispetto al codice sorgente o ha un nome diverso, usare l'opzione del /reference compilatore per farvi riferimento.

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

    Se si compila un singolo progetto, è possibile combinare i passaggi della compilazione della std libreria standard e std.compat dei moduli denominati aggiungendo "%VCToolsInstallDir%\modules\std.ixx" e "%VCToolsInstallDir%\modules\std.compat.ixx" (in questo ordine) 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 compilarli tutti contemporaneamente, assicurarsi di inserirli prima di tutti i .cpp file che li usano e specificare di denominare /Fe il compilato exe come illustrato in questo esempio:

    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 essere. È 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 denominati della libreria standard

Il controllo delle versioni per i moduli denominati è uguale a quello delle intestazioni. I .ixx file di modulo denominati vengono installati insieme alle intestazioni, ad esempio , "%VCToolsInstallDir%\modules\std.ixxche si risolve 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 stesura di questo articolo. 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 combinare e associare le unità di intestazione e i moduli denominati. Ad esempio, non import <vector>; e import std; nello stesso file.

Non combinare e associare l'importazione di file di intestazione della libreria standard C++ e 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 solo la versione <cmath>della libreria standard C++.

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

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

Se è necessario utilizzare la errno macro , #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, NANad esempio , INFINITYe INT_MIN sono definite da <limits.h>, che è possibile includere. Tuttavia, se è import std; possibile usare numeric_limits<double>::quiet_NaN() e numeric_limits<double>::infinity() anziché NAN e INFINITY, anziché std::numeric_limits<int>::min()INT_MIN.

Riepilogo

In questa esercitazione è stata importata la libreria standard usando i moduli. Informazioni sulla creazione e l'importazione di moduli personalizzati nell'esercitazione sui moduli denominati in C++.

Vedi anche

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