Partager via


Procédure pas à pas : Générer et importer des unités d’en-tête dans Microsoft Visual C++

Cet article traite de la génération et de l’importation d’unités d’en-tête avec Visual Studio 2022. Pour savoir comment importer des en-têtes de bibliothèque standard C++ en tant qu’unités d’en-tête, consultez Procédure pas à pas : Importer des bibliothèques STL en tant qu’unités d’en-tête. Pour découvrir un moyen encore plus rapide et plus robuste d’importer la bibliothèque standard, consultez Tutoriel : Importer la bibliothèque standard C++ à l’aide de modules.

Les unités d’en-tête sont l’alternative recommandée aux fichiers d’en-tête précompilés (PCH). Les unités d’en-tête sont plus faciles à configurer et à utiliser, sont beaucoup plus petites sur le disque, offrent des avantages similaires aux performances et sont plus flexibles qu’un PCH partagé.

Pour comparer les unités d’en-tête avec d’autres façons d’inclure des fonctionnalités dans vos programmes, consultez Comparer les unités d’en-tête, les modules et les en-têtes précompilés.

Prérequis

Pour utiliser des unités d’en-tête, vous avez besoin de Visual Studio 2019 16.10 ou version ultérieure.

Qu’est-ce qu’une unité d’en-tête ?

Une unité d’en-tête est une représentation binaire d’un fichier d’en-tête. Une unité d’en-tête se termine par une .ifc extension. Le même format est utilisé pour les modules nommés.

Une différence importante entre une unité d’en-tête et un fichier d’en-tête est qu’une unité d’en-tête n’est pas affectée par les définitions de macro en dehors de l’unité d’en-tête. Autrement dit, vous ne pouvez pas définir un symbole de préprocesseur qui entraîne le comportement de l’unité d’en-tête différemment. Au moment où vous importez l’unité d’en-tête, l’unité d’en-tête est déjà compilée. C’est différent de la façon dont un #include fichier est traité. Un fichier inclus peut être affecté par une définition de macro en dehors du fichier d’en-tête, car le fichier d’en-tête passe par le préprocesseur lorsque vous compilez le fichier source qui l’inclut.

Les unités d’en-tête peuvent être importées dans n’importe quel ordre, ce qui n’est pas vrai des fichiers d’en-tête. L’ordre des fichiers d’en-tête importe, car les définitions de macro définies dans un fichier d’en-tête peuvent affecter un fichier d’en-tête suivant. Les définitions de macro dans une unité d’en-tête ne peuvent pas affecter une autre unité d’en-tête.

Tout ce qui est visible à partir d’un fichier d’en-tête est également visible à partir d’une unité d’en-tête, y compris les macros définies dans l’unité d’en-tête.

Un fichier d’en-tête doit être traduit en unité d’en-tête avant de pouvoir être importé. L’avantage des unités d’en-tête par rapport aux fichiers d’en-tête précompilés (PCH) est qu’elles peuvent être utilisées dans les builds distribuées. Tant que vous compilez et .ifc le programme qui l’importe avec le même compilateur et ciblez la même plateforme et la même architecture, une unité d’en-tête produite sur un ordinateur peut être consommée sur un autre. Contrairement à un PCH, lorsqu’une unité d’en-tête change, seule celle-ci et ce qui dépend de celle-ci sont reconstruites. Les unités d’en-tête peuvent être jusqu’à un ordre de grandeur inférieur à celui d’un .pch.

Les unités d’en-tête imposent moins de contraintes sur les similitudes requises des combinaisons de commutateurs de compilateur utilisées pour créer l’unité d’en-tête et compiler le code qui l’utilise qu’un PCH. Toutefois, certaines combinaisons de commutateurs et définitions de macro peuvent créer des violations de la règle de définition (ODR) entre différentes unités de traduction.

Enfin, les unités d’en-tête sont plus flexibles qu’un PCH. Avec un PCH, vous ne pouvez pas choisir d’apporter l’un des en-têtes dans le compilateur PCH- le traite tous. Avec les unités d’en-tête, même lorsque vous les compilez ensemble dans une bibliothèque statique, vous apportez uniquement le contenu de l’unité d’en-tête que vous importez dans votre application.

Les unités d’en-tête sont une étape entre les fichiers d’en-tête et les modules C++20. Ils offrent certains des avantages des modules. Ils sont plus robustes, car en dehors des définitions de macros ne les affectent pas, vous pouvez donc les importer dans n’importe quel ordre. Et le compilateur peut les traiter plus rapidement que les fichiers d’en-tête. Mais les unités d’en-tête n’ont pas tous les avantages des modules, car les unités d’en-tête exposent les macros définies dans ces modules (les modules ne le font pas). Contrairement aux modules, il n’existe aucun moyen de masquer l’implémentation privée dans une unité d’en-tête. Pour indiquer une implémentation privée avec des fichiers d’en-tête, différentes techniques sont utilisées comme l’ajout de traits de soulignements de début aux noms ou la mise en place d’éléments dans un espace de noms d’implémentation. Un module n’expose pas d’implémentation privée sous n’importe quel formulaire. Vous n’avez donc pas besoin de le faire.

Envisagez de remplacer vos en-têtes précompilés par des unités d’en-tête. Vous bénéficiez également de l’avantage de vitesse, mais avec d’autres avantages d’hygiène et de flexibilité du code.

Méthodes de compilation d’une unité d’en-tête

Il existe plusieurs façons de compiler un fichier dans une unité d’en-tête :

  • Générez un projet d’unité d’en-tête partagée. Nous vous recommandons cette approche, car elle offre un contrôle plus important sur l’organisation et la réutilisation des unités d’en-tête importées. Créez un projet de bibliothèque statique qui contient les unités d’en-tête souhaitées, puis référencez-le pour importer les unités d’en-tête. Pour obtenir une procédure pas à pas de cette approche, consultez Générer un projet de bibliothèque statique d’unité d’en-tête pour les unités d’en-tête.

  • Choisissez des fichiers individuels à traduire en unités d’en-tête. Cette approche vous donne un contrôle de fichier par fichier sur ce qui est traité comme une unité d’en-tête. Il est également utile de compiler un fichier en tant qu’unité d’en-tête, car il n’a pas l’extension par défaut (.ixx, .cppm, .h, ), .hppne serait pas normalement compilé dans une unité d’en-tête. Cette approche est illustrée dans cette procédure pas à pas. Pour commencer, consultez l’approche 1 : Traduire un fichier spécifique en unité d’en-tête.

  • Recherchez et générez automatiquement des unités d’en-tête. Cette approche est pratique, mais est mieux adaptée aux projets plus petits, car elle ne garantit pas un débit de build optimal. Pour plus d’informations sur cette approche, consultez l’approche 2 : analyser automatiquement les unités d’en-tête.

  • Comme mentionné dans l’introduction, vous pouvez générer et importer des fichiers d’en-tête STL en tant qu’unités d’en-tête, et traiter #include automatiquement les en-têtes de bibliothèque STL comme import sans réécrire votre code. Pour voir comment, visitez la procédure pas à pas : importer des bibliothèques STL en tant qu’unités d’en-tête.

Approche 1 : Traduire un fichier spécifique en unité d’en-tête

Cette section montre comment choisir un fichier spécifique à traduire en unité d’en-tête. Compilez un fichier d’en-tête en tant qu’unité d’en-tête en suivant les étapes suivantes dans Visual Studio :

  1. Créez un projet d’application console C++.

  2. Remplacez le contenu du fichier source comme suit :

    #include "Pythagorean.h"
    
    int main()
    {
        PrintPythagoreanTriple(2,3);
        return 0;
    }
    
  3. Ajoutez un fichier d’en-tête appelé Pythagorean.h , puis remplacez son contenu par ce code :

    #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
    

Définir les propriétés du projet

Pour activer les unités d’en-tête, commencez par définir la norme de langage C++ sur /std:c++20 ou une version ultérieure en procédant comme suit :

  1. Dans Explorateur de solutions, cliquez avec le bouton droit sur le nom du projet et choisissez Propriétés.
  2. Dans le volet gauche de la fenêtre des pages de propriétés du projet, sélectionnez Propriétés>de configuration générales.
  3. Dans la liste déroulante C++ Language Standard , sélectionnez ISO C++20 Standard (/std :c++20) ou version ultérieure. Choisissez Ok pour fermer la boîte de dialogue.

Compilez le fichier d’en-tête en tant qu’unité d’en-tête :

  1. Dans Explorateur de solutions, sélectionnez le fichier que vous souhaitez compiler en tant qu’unité d’en-tête (dans ce cas, Pythagorean.h). Cliquez avec le bouton droit sur le fichier et choisissez Propriétés.

  2. Définissez la liste déroulante Type d’élément général>des propriétés>de configuration sur le compilateur C/C++ et choisissez Ok.

    Capture d’écran montrant la modification du type d’élément en compilateur C/C++.

Lorsque vous générez ce projet plus loin dans cette procédure pas à pas, Pythagorean.h sera traduit en une unité d’en-tête. Il est traduit en unité d’en-tête, car le type d’élément de ce fichier d’en-tête est défini sur le compilateur C/C++, et parce que l’action par défaut pour .h et .hpp les fichiers définis de cette façon consiste à traduire le fichier en unité d’en-tête.

Remarque

Cette procédure pas à pas n’est pas nécessaire, mais elle est fournie pour vos informations. Pour compiler un fichier en tant qu’unité d’en-tête qui n’a pas d’extension de fichier d’unité d’en-tête par défaut, par .cpp exemple, définissez les propriétés>de configuration C/C++>Advanced>Compile As to Compile as C++ Header Unit (/exportHeader) :Capture d’écran montrant la modification des propriétés > de configuration C/C++ > Advanced > Compile As to Compile as C++ Header Unit (/exportHeader).

Modifier votre code pour importer l’unité d’en-tête

  1. Dans le fichier source de l’exemple de projet, passez #include "Pythagorean.h" à import "Pythagorean.h"; Ne pas oublier le point-virgule de fin. Il est nécessaire pour import les instructions. Étant donné qu’il s’agit d’un fichier d’en-tête dans un répertoire local du projet, nous avons utilisé des guillemets avec l’instruction import : import "file";. Dans vos propres projets, pour compiler une unité d’en-tête à partir d’un en-tête système, utilisez des crochets d’angle : import <file>;

  2. Générez la solution en sélectionnant Générer>Générer la solution dans le menu principal. Exécutez-la pour voir qu’elle produit la sortie attendue : Pythagorean triple a:2 b:3 c:13

Dans vos propres projets, répétez ce processus pour compiler les fichiers d’en-tête que vous souhaitez importer en tant qu’unités d’en-tête.

Si vous souhaitez convertir uniquement quelques fichiers d’en-tête en unités d’en-tête, cette approche est bonne. Toutefois, si vous avez de nombreux fichiers d’en-tête que vous souhaitez compiler et que la perte potentielle de performances de build est compensée par la commodité de leur gestion automatique par le système de build, consultez la section suivante.

Si vous souhaitez importer spécifiquement des en-têtes de bibliothèque STL en tant qu’unités d’en-tête, consultez Procédure pas à pas : Importer des bibliothèques STL en tant qu’unités d’en-tête.

Approche 2 : rechercher et générer automatiquement des unités d’en-tête

Étant donné qu’il faut du temps pour analyser tous vos fichiers sources pour les unités d’en-tête et le temps de les générer, l’approche suivante convient le mieux aux projets plus petits. Il ne garantit pas un débit de build optimal.

Cette approche combine deux paramètres de projet Visual Studio :

  • L’analyse des sources pour les dépendances de module entraîne l’appel du système de build au compilateur pour s’assurer que tous les modules et unités d’en-tête importés sont générés avant de compiler les fichiers qui en dépendent. Lorsqu’ils sont combinés avec Translate Include to Imports, tous les fichiers d’en-tête inclus dans votre source qui sont également spécifiés dans un header-units.json fichier situé dans le même répertoire que le fichier d’en-tête sont compilés en unités d’en-tête.
  • Translate Include to Imports traite un fichier d’en-tête comme un import fichier d’en-tête si le #include fait référence à un fichier d’en-tête pouvant être compilé en tant qu’unité d’en-tête (comme spécifié dans un header-units.json fichier) et une unité d’en-tête compilée est disponible pour le fichier d’en-tête. Sinon, le fichier d’en-tête est traité comme une normale #include. Le header-units.json fichier est utilisé pour générer automatiquement des unités d’en-tête pour chaque #include, sans duplication de symboles.

Vous pouvez activer ces paramètres dans les propriétés de votre projet. Pour ce faire, cliquez avec le bouton droit sur le projet dans le Explorateur de solutions et choisissez Propriétés. Ensuite, choisissez Propriétés>de configuration C/C++>Général.

Capture d’écran montrant l’écran des propriétés du projet avec Configuration mise en surbrillance et Toutes les configurations sélectionnées. Sous C/C++ > Général, les sources d’analyse pour les dépendances de module sont mises en surbrillance et définies sur Oui, et Traduire les éléments inclus en importations sont mis en surbrillance et définis sur Oui (/translateInclude)

Les sources d’analyse des dépendances de module peuvent être définies pour tous les fichiers du projet dans propriétés du projet, comme indiqué ici, ou pour des fichiers individuels dans propriétés de fichier. Les modules et les unités d’en-tête sont toujours analysés. Définissez cette option lorsque vous disposez d’un .cpp fichier qui importe les unités d’en-tête que vous souhaitez générer automatiquement et qui ne sont peut-être pas encore générées.

Ces paramètres fonctionnent ensemble pour générer et importer automatiquement des unités d’en-tête dans les conditions suivantes :

  • Analyser les sources des dépendances de module analyse vos sources pour les fichiers et leurs dépendances qui peuvent être traitées comme des unités d’en-tête. Les fichiers qui ont l’extension .ixxet les fichiers qui ont leurs propriétés>de fichier C/C++>Compile En tant que propriété Compile en tant qu’unité d’en-tête C++ (/export) sont toujours analysés indépendamment de ce paramètre. Le compilateur recherche import également des instructions pour identifier les dépendances d’unité d’en-tête. S’il /translateInclude est spécifié, le compilateur analyse également les #include directives qui sont également spécifiées dans un header-units.json fichier pour traiter comme des unités d’en-tête. Une graphe des dépendances est générée de tous les modules et unités d’en-tête dans votre projet.
  • Translate Include to Imports When the compiler rencontre une #include instruction, and a matching header unit file (.ifc) exists for the specified header file, the compiler imports the header unit instead of treat the header file as an #include. Lorsqu’il est combiné à l’analyse des dépendances, le compilateur recherche tous les fichiers d’en-tête qui peuvent être compilés en unités d’en-tête. Une liste verte est consultée par le compilateur pour déterminer les fichiers d’en-tête pouvant être compilés en unités d’en-tête. Cette liste est stockée dans un header-units.json fichier qui doit se trouver dans le même répertoire que le fichier inclus. Vous pouvez voir un header-units.json exemple de fichier sous le répertoire d’installation de Visual Studio. Par exemple, %ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.30.30705\include\header-units.json est utilisé par le compilateur pour déterminer si un en-tête de bibliothèque de modèles standard peut être compilé dans une unité d’en-tête. Cette fonctionnalité existe pour servir de pont avec du code hérité pour bénéficier de certains avantages des unités d’en-tête.

Le header-units.json fichier sert à deux fins. En plus de spécifier les fichiers d’en-tête pouvant être compilés en unités d’en-tête, il réduit les symboles dupliqués pour augmenter le débit de génération. Pour plus d’informations sur la duplication de symboles, consultez la référence header-units.json C++.

Ces commutateurs et header-unit.json offrent certains des avantages des unités d’en-tête. La commodité est fournie au coût du débit de build. Cette approche peut ne pas être la meilleure pour les projets plus volumineux, car elle ne garantit pas de temps de génération optimaux. En outre, les mêmes fichiers d’en-tête peuvent être retratés à plusieurs reprises, ce qui augmente le temps de génération. Toutefois, la commodité peut être utile en fonction du projet.

Ces fonctionnalités sont conçues pour le code hérité. Pour le nouveau code, passez aux modules au lieu d’unités d’en-tête ou #include de fichiers. Pour obtenir un didacticiel sur l’utilisation de modules, consultez le didacticiel sur les modules Name (C++).

Pour obtenir un exemple de la façon dont cette technique est utilisée pour importer des fichiers d’en-tête STL en tant qu’unités d’en-tête, consultez Procédure pas à pas : Importer des bibliothèques STL en tant qu’unités d’en-tête.

Implications du préprocesseur

Le préprocesseur standard C99/C++11 est requis pour créer et utiliser des unités d’en-tête. Le compilateur active le nouveau préprocesseur conforme C99/C++11 lors de la compilation d’unités d’en-tête en ajoutant /Zc:preprocessor implicitement à la ligne de commande chaque fois qu’une forme de /exportHeader données est utilisée. Toute tentative de désactivation entraîne une erreur de compilation.

L’activation du nouveau préprocesseur affecte le traitement des macros variadiciques. Pour plus d’informations, consultez la section des notes sur les macros Variadic.

Voir aussi

/translateInclude
/exportHeader
/headerUnit
header-units.json
Comparer les unités d’en-tête, les modules et les en-têtes précompilés
Vue d’ensemble des modules dans C++
Tutoriel : Importer la bibliothèque standard C++ en utilisant des modules
Procédure pas à pas : Importer des bibliothèques STL en tant qu’unités d’en-tête