Procedura dettagliata: Compilare e importare unità di intestazione in Microsoft Visual C++
Questo articolo illustra la compilazione e l'importazione di unità di intestazione con Visual Studio 2022. Per informazioni su come importare le intestazioni della libreria standard C++ come unità di intestazione, vedere Procedura dettagliata: Importare librerie STL come unità di intestazione. Per un modo ancora più rapido e affidabile per importare la libreria standard, vedere Esercitazione: Importare la libreria standard C++ usando i moduli.
Le unità di intestazione sono l'alternativa consigliata ai file di intestazione precompilati (PCH). Le unità di intestazione sono più facili da configurare e usare, sono notevolmente più piccole su disco, offrono vantaggi di prestazioni simili e sono più flessibili rispetto a un PCH condiviso.
Per confrontare le unità di intestazione con altri modi per includere funzionalità nei programmi, vedere Confrontare unità di intestazione, moduli e intestazioni precompilate.
Prerequisiti
Per usare le unità di intestazione, è necessario Visual Studio 2019 16.10 o versione successiva.
Che cos'è un'unità di intestazione
Un'unità di intestazione è una rappresentazione binaria di un file di intestazione. Un'unità di intestazione termina con un'estensione .ifc
. Lo stesso formato viene usato per i moduli denominati.
Una differenza importante tra un'unità di intestazione e un file di intestazione è che un'unità di intestazione non è influenzata dalle definizioni di macro all'esterno dell'unità di intestazione. Ciò significa che non è possibile definire un simbolo del preprocessore che determina il comportamento dell'unità di intestazione in modo diverso. Al momento dell'importazione dell'unità di intestazione, l'unità di intestazione è già compilata. Questo è diverso dal modo in cui un #include
file viene trattato. Un file incluso può essere interessato da una definizione di macro all'esterno del file di intestazione perché il file di intestazione passa attraverso il preprocessore quando si compila il file di origine che lo include.
Le unità di intestazione possono essere importate in qualsiasi ordine, che non è vero dei file di intestazione. L'ordine dei file di intestazione è importante perché le definizioni di macro definite in un file di intestazione potrebbero influire su un file di intestazione successivo. Le definizioni di macro in un'unità di intestazione non possono influire su un'altra unità di intestazione.
Tutto ciò che è visibile da un file di intestazione è visibile anche da un'unità di intestazione, incluse le macro definite nell'unità di intestazione.
Un file di intestazione deve essere convertito in un'unità di intestazione prima di poterlo importare. Un vantaggio delle unità di intestazione rispetto ai file di intestazione precompilati (PCH) è che possono essere usati nelle compilazioni distribuite. Finché si compila e .ifc
il programma che lo importa con lo stesso compilatore e si usa la stessa piattaforma e la stessa architettura, un'unità di intestazione prodotta in un computer può essere utilizzata su un altro. A differenza di un PCH, quando un'unità di intestazione cambia, solo esso e ciò che dipende da esso vengono ricompilati. Le unità di intestazione possono essere fino a un ordine di grandezza inferiore alle dimensioni di un oggetto .pch
.
Le unità di intestazione impongono meno vincoli sulle analogie richieste delle combinazioni di commutatori del compilatore usate per creare l'unità di intestazione e compilare il codice che lo utilizza rispetto a un PCH. Tuttavia, alcune combinazioni di opzioni e definizioni di macro potrebbero creare violazioni di una regola di definizione (ODR) tra varie unità di conversione.
Infine, le unità di intestazione sono più flessibili di un PCH. Con un PCH, non è possibile scegliere di inserire solo una delle intestazioni nel PCH: il compilatore li elabora tutti. Con le unità di intestazione, anche quando le si compilano insieme in una libreria statica, è possibile importare solo il contenuto dell'unità di intestazione importata nell'applicazione.
Le unità di intestazione sono un passaggio tra i file di intestazione e i moduli C++20. Offrono alcuni dei vantaggi dei moduli. Sono più affidabili perché le definizioni di macro esterne non influiscono su di esse, quindi è possibile importarle in qualsiasi ordine. E il compilatore può elaborarli più velocemente rispetto ai file di intestazione. Tuttavia, le unità di intestazione non presentano tutti i vantaggi dei moduli perché le unità di intestazione espongono le macro definite all'interno di esse (i moduli non lo sono). A differenza dei moduli, non è possibile nascondere l'implementazione privata in un'unità di intestazione. Per indicare l'implementazione privata con i file di intestazione, vengono usate diverse tecniche come l'aggiunta di caratteri di sottolineatura iniziali ai nomi o l'inserimento di elementi in uno spazio dei nomi di implementazione. Un modulo non espone l'implementazione privata in alcun formato, quindi non è necessario eseguire questa operazione.
È consigliabile sostituire le intestazioni precompilate con le unità di intestazione. Si ottiene lo stesso vantaggio di velocità, ma anche con altri vantaggi di igiene e flessibilità del codice.
Modi per compilare un'unità di intestazione
Esistono diversi modi per compilare un file in un'unità di intestazione:
Compilare un progetto di unità di intestazione condivisa. È consigliabile usare questo approccio perché offre maggiore controllo sull'organizzazione e il riutilizzo delle unità di intestazione importate. Creare un progetto di libreria statica contenente le unità di intestazione desiderate e quindi farvi riferimento per importare le unità di intestazione. Per una procedura dettagliata di questo approccio, vedere Creare un progetto di libreria statica dell'unità di intestazione per le unità di intestazione.
Scegliere singoli file da convertire in unità di intestazione. Questo approccio consente di controllare file per file su ciò che viene considerato come un'unità di intestazione. È utile anche quando è necessario compilare un file come unità di intestazione, perché non ha l'estensione predefinita (
.ixx
,.cppm
,.h
,.hpp
), in genere non viene compilata in un'unità di intestazione. Questo approccio è illustrato in questa procedura dettagliata. Per iniziare, vedere Approccio 1: Tradurre un file specifico in un'unità di intestazione.Eseguire automaticamente l'analisi delle unità di intestazione e di compilazione. Questo approccio è pratico, ma è più adatto ai progetti più piccoli perché non garantisce una velocità effettiva di compilazione ottimale. Per informazioni dettagliate su questo approccio, vedere Approccio 2: Analisi automatica delle unità di intestazione.
Come accennato nell'introduzione, è possibile compilare e importare file di intestazione STL come unità di intestazione e considerare
#include
automaticamente le intestazioni della libreria STL comeimport
senza riscrivere il codice. Per informazioni su come, vedere Procedura dettagliata: Importare librerie STL come unità di intestazione.
Approccio 1: Convertire un file specifico in un'unità di intestazione
Questa sezione illustra come scegliere un file specifico da convertire in un'unità di intestazione. Compilare un file di intestazione come unità di intestazione seguendo questa procedura in Visual Studio:
Creare un nuovo progetto di app console C++.
Sostituire il contenuto del file di origine come indicato di seguito:
#include "Pythagorean.h" int main() { PrintPythagoreanTriple(2,3); return 0; }
Aggiungere un file di intestazione denominato
Pythagorean.h
e quindi sostituirlo con questo codice:#ifndef PYTHAGOREAN #define PYTHAGOREAN #include <iostream> inline void PrintPythagoreanTriple(int a, int b) { std::cout << "Pythagorean triple a:" << a << " b:" << b << " c:" << a*a + b*b << std::endl; } #endif
Impostare le proprietà del progetto
Per abilitare le unità di intestazione, impostare prima lo standard del linguaggio C++ su /std:c++20
o versione successiva con la procedura seguente:
- In Esplora soluzioni fare clic con il pulsante destro del mouse sul nome del progetto e scegliere Proprietà.
- Nel riquadro sinistro della finestra delle pagine delle proprietà del progetto selezionare Proprietà>di configurazione Generale.
- Nell'elenco a discesa Standard del linguaggio C++ selezionare ISO C++20 Standard (/std:c++20) o versione successiva. Scegliere OK per chiudere la finestra di dialogo.
Compilare il file di intestazione come unità di intestazione:
In Esplora soluzioni selezionare il file che si vuole compilare come unità di intestazione (in questo caso,
Pythagorean.h
). Fare clic con il pulsante destro del mouse sul file e scegliere Proprietà.Impostare l'elenco a discesa Tipo di elemento generale>delle proprietà>di configurazione sul compilatore C/C++ e scegliere OK.
Quando si compila questo progetto più avanti in questa procedura dettagliata, Pythagorean.h
verrà convertito in un'unità di intestazione. Viene convertito in un'unità di intestazione perché il tipo di elemento per questo file di intestazione è impostato sul compilatore C/C++ e poiché l'azione predefinita per .h
e .hpp
i file impostati in questo modo consiste nel convertire il file in un'unità di intestazione.
Nota
Questa operazione non è necessaria per questa procedura dettagliata, ma viene fornita per le informazioni. Per compilare un file come unità di intestazione che non dispone di un'estensione di unità di intestazione predefinita, .cpp
ad esempio impostare le proprietà>di configurazione C/C++>Advanced>Compile As to Compile as to Compile as (/exportHeader):
Modificare il codice per importare l'unità di intestazione
Nel file di origine per il progetto di esempio passare
#include "Pythagorean.h"
aimport "Pythagorean.h";
Non dimenticare il punto e virgola finale. È obbligatorio perimport
le istruzioni. Poiché si tratta di un file di intestazione in una directory locale del progetto, sono stati usati virgolette con l'istruzioneimport
:import "file";
. Nei progetti personalizzati, per compilare un'unità di intestazione da un'intestazione di sistema, usare parentesi angolari:import <file>;
Compilare la soluzione selezionando Compila>soluzione nel menu principale. Eseguirlo per verificare che produa l'output previsto:
Pythagorean triple a:2 b:3 c:13
Nei propri progetti ripetere questo processo per compilare i file di intestazione da importare come unità di intestazione.
Se si desidera convertire solo alcuni file di intestazione in unità di intestazione, questo approccio è valido. Tuttavia, se si hanno molti file di intestazione da compilare e la potenziale perdita di prestazioni di compilazione è superiore alla praticità di gestire automaticamente il sistema di compilazione, vedere la sezione seguente.
Se si è interessati a importare in modo specifico le intestazioni della libreria STL come unità di intestazione, vedere Procedura dettagliata: Importare librerie STL come unità di intestazione.
Approccio 2: analizzare automaticamente le unità di intestazione e di compilazione
Poiché è necessario tempo per analizzare tutti i file di origine per le unità di intestazione e il tempo necessario per compilarli, l'approccio seguente è più adatto per i progetti più piccoli. Non garantisce una velocità effettiva di compilazione ottimale.
Questo approccio combina due impostazioni del progetto di Visual Studio:
- L'analisi delle origini per le dipendenze del modulo fa sì che il sistema di compilazione chiami il compilatore per assicurarsi che tutti i moduli importati e le unità di intestazione vengano compilati prima di compilare i file che dipendono da essi. Se combinato con Translate Includes to Imports, tutti i file di intestazione inclusi nell'origine specificati anche in un
header-units.json
file che si trova nella stessa directory del file di intestazione vengono compilati in unità di intestazione. - Translate Includes to Imports considera un file di intestazione come se
import
fa#include
riferimento a un file di intestazione che può essere compilato come unità di intestazione (come specificato in unheader-units.json
file) ed è disponibile un'unità di intestazione compilata per il file di intestazione. In caso contrario, il file di intestazione viene considerato come normale#include
. Ilheader-units.json
file viene usato per compilare automaticamente le unità di intestazione per ogni#include
, senza duplicazione dei simboli.
È possibile attivare queste impostazioni nelle proprietà del progetto. A tale scopo, fare clic con il pulsante destro del mouse sul progetto nel Esplora soluzioni e scegliere Proprietà. Scegliere quindi Proprietà>di configurazione C/C++>Generale.
Le origini di analisi delle dipendenze del modulo possono essere impostate per tutti i file del progetto in Proprietà progetto, come illustrato di seguito o per singoli file in Proprietà file. I moduli e le unità di intestazione vengono sempre analizzati. Impostare questa opzione quando si dispone di un .cpp
file che importa unità di intestazione che si desidera compilare automaticamente e potrebbe non essere ancora compilato.
Queste impostazioni interagiscono per creare e importare automaticamente le unità di intestazione in queste condizioni:
- Analizza le origini per le dipendenze del modulo analizza le origini per i file e le relative dipendenze che possono essere considerate come unità di intestazione. I file con estensione
.ixx
e i file con le relative proprietà>File C/C++>Compile As sono impostati su Compila come unità di intestazione C++ (/esportazione) vengono sempre analizzati indipendentemente da questa impostazione. Il compilatore cercaimport
anche le istruzioni per identificare le dipendenze dell'unità di intestazione. Se/translateInclude
viene specificato, il compilatore analizza anche le#include
direttive specificate in unheader-units.json
file da considerare come unità di intestazione. Un grafico delle dipendenze è costituito da tutti i moduli e le unità di intestazione del progetto. - Translate Includes to Imports Quando il compilatore rileva un'istruzione
#include
e esiste un file di unità di intestazione corrispondente (.ifc
) per il file di intestazione specificato, il compilatore importa l'unità di intestazione invece di trattare il file di intestazione come .#include
In combinazione con l'analisi delle dipendenze, il compilatore trova tutti i file di intestazione che possono essere compilati in unità di intestazione. Un elenco di elementi consentiti viene consultato dal compilatore per decidere quali file di intestazione possono essere compilati in unità di intestazione. Questo elenco viene archiviato in unheader-units.json
file che deve trovarsi nella stessa directory del file incluso. È possibile visualizzare un esempio di fileheader-units.json
nella directory di installazione per Visual Studio. Ad esempio,%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json
viene usato dal compilatore per determinare se un'intestazione della libreria di modelli standard può essere compilata in un'unità di intestazione. Questa funzionalità esiste per fungere da bridge con codice legacy per ottenere alcuni vantaggi delle unità di intestazione.
Il header-units.json
file serve due scopi. Oltre a specificare quali file di intestazione possono essere compilati in unità di intestazione, riduce al minimo i simboli duplicati per aumentare la velocità effettiva di compilazione. Per altre informazioni sulla duplicazione dei simboli, vedere Riferimenti header-units.json C++.
Queste opzioni e header-unit.json
offrono alcuni dei vantaggi delle unità di intestazione. La praticità è il costo della velocità effettiva di compilazione. Questo approccio potrebbe non essere il migliore per i progetti di grandi dimensioni perché non garantisce tempi di compilazione ottimali. Inoltre, gli stessi file di intestazione potrebbero essere rielaborati ripetutamente, aumentando il tempo di compilazione. Tuttavia, la praticità potrebbe essere valsa la pena a seconda del progetto.
Queste funzionalità sono progettate per il codice legacy. Per il nuovo codice, passare ai moduli anziché alle unità di intestazione o #include
ai file. Per un'esercitazione sull'uso dei moduli, vedere Esercitazione sui moduli dei nomi (C++).
Per un esempio di come questa tecnica viene usata per importare i file di intestazione STL come unità di intestazione, vedere Procedura dettagliata: Importare librerie STL come unità di intestazione.
Implicazioni del preprocessore
Il preprocessore conforme allo standard C99/C++11 è necessario per creare e usare unità di intestazione. Il compilatore abilita il nuovo preprocessore conforme A C99/C++11 durante la compilazione di unità di intestazione aggiungendo /Zc:preprocessor
in modo implicito alla riga di comando ogni volta che viene usata qualsiasi forma di /exportHeader
. Il tentativo di disattivarlo genererà un errore di compilazione.
L'abilitazione del nuovo preprocessore influisce sull'elaborazione di macro variadic. Per altre informazioni, vedere la sezione Osservazioni sulle macro variadic.
Vedi anche
/translateInclude
/exportHeader
/headerUnit
header-units.json
Confrontare unità di intestazione, moduli e intestazioni precompilate
Panoramica dei moduli in C++
Esercitazione: Importare la libreria standard C++ usando i moduli
Procedura dettagliata: Importare librerie STL come unità di intestazione