Partager via


Tutoriel : Importer la bibliothèque standard C++ en utilisant des modules à partir de la ligne de commande

Découvrez comment importer la bibliothèque standard C++ en utilisant des modules de bibliothèque C++. Cela permet une compilation plus rapide et est plus robuste que l’utilisation de fichiers d’en-tête, d’unités d’en-tête ou d’en-têtes précompilés (PCH).

Dans ce tutoriel, découvrez :

  • Comment importer la bibliothèque standard en tant que module à partir de la ligne de commande.
  • Les avantages en matière de performances et d’utilisabilité des modules.
  • Les deux modules std et std.compat de bibliothèque standard, et ce qui les différencie.

Prérequis

Ce tutoriel nécessite Visual Studio 2022 17.5 ou ultérieur.

Présentation des modules de bibliothèque standard

Les fichiers d’en-tête souffrent d’une sémantique qui peut changer en fonction des définitions des macros, de l’ordre dans lequel vous les incluez et de la lenteur de leur compilation. Les modules résolvent ces problèmes.

Il est désormais possible d’importer la bibliothèque standard sous la forme d’un module et non plus d’un enchevêtrement de fichiers d’en-tête. Cette méthode est beaucoup plus rapide et plus robuste que l’inclusion de fichiers d’en-tête, d’unités d’en-tête ou d’en-têtes précompilés (PCH).

La bibliothèque standard C++23 introduit les deux modules nommés std et std.compat :

  • std exporte les déclarations et les noms définis dans l’espace de noms std de la bibliothèque standard C++, comme std::vector. Il exporte également le contenu des en-têtes de wrapper C, comme <cstdio> et <cstdlib>, qui fournissent des fonctions comme std::printf(). Les fonctions C définies dans l’espace de noms global, comme ::printf(), ne sont pas exportées. Cela permet d’améliorer les situations où l’inclusion d’un en-tête de wrapper C comme <cstdio> inclut également des fichiers d’en-tête C comme stdio.h, qui intègrent les versions de l’espace de noms global C. Ce n’est pas un problème si vous importez std.
  • std.compat exporte tout dans std et ajoute les espaces de noms globaux du runtime C, comme ::printf, ::fopen, ::size_t, ::strlen, etc. Le module std.compat facilite le travail avec les codebases qui référencent de nombreuses fonctions/types du runtime C dans l’espace de noms global.

Le compilateur importe l’ensemble de la bibliothèque standard quand vous utilisez import std; ou import std.compat;, et il le fait plus rapidement que dans le cas de l’insertion d’un seul fichier d’en-tête. Il est plus rapide d’intégrer toute la bibliothèque standard avec import std; (ou import std.compat) qu’avec par exemple #include <vector>.

Comme les modules nommés n’exposent pas de macros, des macros comme assert, errno, offsetof, va_arg et d’autres ne sont pas disponibles quand vous importez std ou std.compat. Consultez Considérations relatives aux modules nommés de la bibliothèque standard pour obtenir des solutions de contournement.

À propos des modules C++

Les fichiers d’en-tête ont été utilisés pour partager les déclarations et les définitions entre les fichiers sources en C++. Avant les modules de bibliothèque standard, vous deviez inclure la partie de la bibliothèque standard dont vous aviez besoin, avec une directive comme #include <vector>. Les fichiers d’en-tête sont fragiles et difficiles à composer, car leur sémantique peut changer selon l’ordre dans lequel vous les incluez ou si certaines macros sont définies. Ils ralentissent également la compilation, car ils sont retraités par chaque fichier source qui les inclut.

C++20 introduit une alternative moderne appelée modules. En C++23, nous avons pu capitaliser sur la prise en charge des modules pour introduire les modules nommés destinés à représenter la bibliothèque standard.

Comme les fichiers d’en-tête, les modules vous permettent de partager des déclarations et des définitions entre des fichiers sources. Mais contrairement aux fichiers d’en-tête, les modules ne sont pas fragiles et sont plus faciles à composer, car leur sémantique ne change pas en raison des définitions de macros ou de l’ordre dans lequel vous les importez. Le compilateur peut traiter les modules beaucoup plus rapidement que les fichiers #include et il utilise moins de mémoire au moment de la compilation. Les modules nommés n’exposent pas de définitions de macros ni de détails d’implémentation privés.

Pour plus d’informations sur les modules, consultez Vue d’ensemble des modules en C++. Cet article traite également de la consommation de la bibliothèque standard C++ sous forme de modules, mais il utilise pour cela une méthode plus ancienne et expérimentale.

Cet article montre la nouvelle et meilleure façon de consommer la bibliothèque standard. Pour plus d’informations sur les autres façons d’utiliser la bibliothèque standard, consultez Comparer les unités d’en-tête, les modules et les en-têtes précompilés.

Importer la bibliothèque standard avec std

Les exemples suivants montrent comment consommer la bibliothèque standard en tant que module en utilisant le compilateur en ligne de commande. Pour plus d’informations sur la façon de le faire dans l’IDE Visual Studio, consultez Créer des modules de bibliothèque standard C++23 ISO.

L’instruction import std; ou import std.compat; importe la bibliothèque standard dans votre application. Cependant, vous d’abord devez compiler les modules nommés de la bibliothèque standard sous une forme binaire. Les étapes suivantes montrent comment procéder.

Exemple : Comment créer et importer std

  1. Ouvrez une invite de commandes des outils natifs x86 pour VS : dans le menu Démarrer de Windows, tapez x86 natif : l’invite doit apparaître dans la liste des applications. Vérifiez que l’invite concerne Visual Studio 2022 version 17.5 ou ultérieure. Vous recevrez des erreurs si vous utilisez la version incorrecte de l’invite. Les exemples utilisés dans ce didacticiel sont destinés au shell CMD.

  2. Créez un répertoire, comme %USERPROFILE%\source\repos\STLModules, et faites-en le répertoire actif. Si vous choisissez un répertoire auquel vous n’avez pas accès en écriture, vous recevrez des erreurs lors de la compilation.

  3. Compilez le module nommé std avec la commande suivante :

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

    Si vous recevez des erreurs, vérifiez que vous utilisez la version correcte de l’invite de commandes.

    Compilez le module nommé std en utilisant les mêmes paramètres de compilateur que ceux que vous prévoyez d’utiliser avec le code qui importe le module généré. Si vous avez une solution multiprojet, vous pouvez compiler une seule fois le module nommé de bibliothèque standard, puis y faire référence depuis tous vos projets en utilisant l’option /reference du compilateur.

    Avec la commande de compilateur précédente, le compilateur produit en sortie deux fichiers :

    • std.ifc est la représentation binaire compilée de l’interface du module nommé que le compilateur consulte pour traiter l’instruction import std;. C’est un artefact uniquement au moment de la compilation. Il n’est pas fourni avec votre application.
    • std.obj contient l’implémentation du module nommé. Ajoutez std.obj à la ligne de commande quand vous compilez l’exemple d’application pour lier statiquement les fonctionnalités de la bibliothèque standard que vous utilisez dans votre application.

    Les commutateurs de ligne de commande clés dans cet exemple sont les suivants :

    Commutateur Signification
    /std:c++:latest Utilisez la dernière version de la norme du langage C++ et de la bibliothèque. Bien que la prise en charge des modules soit disponible sous /std:c++20, vous avez besoin de la dernière version de la bibliothèque standard pour obtenir la prise en charge des modules nommés de la bibliothèque standard.
    /EHsc Utilisez la gestion des exceptions C++, sauf pour les fonctions marquées extern "C".
    /W4 L’utilisation de /W4 est généralement recommandée, en particulier pour les nouveaux projets, car elle active tous les avertissements de niveau 1, 2 et 3, et la plupart des avertissements de niveau 4 (information), ce qui peut vous aider à détecter plus tôt les problèmes potentiels. Elle fournit essentiellement des avertissements de type lint, qui peuvent contribuer à réduire au minimum les défauts du code difficiles à trouver.
    /c Compilez sans liaison, car à ce stade, nous créons simplement l’interface du module nommé binaire.

    Vous pouvez contrôler le nom du fichier objet et le nom du fichier d’interface du module nommé avec les commutateurs suivants :

    • /Fo définit le nom du fichier objet. Par exemple : /Fo:"somethingelse". Par défaut, le compilateur utilise pour le fichier objet le même nom que le fichier source du module (.ixx) que vous compilez. Dans l’exemple, le nom du fichier objet est std.obj par défaut, car nous compilons le fichier de module std.ixx.
    • /ifcOutput définit le nom du fichier d’interface du module nommé (.ifc). Par exemple : /ifcOutput "somethingelse.ifc". Par défaut, le compilateur utilise pour le fichier d’interface du module (.ifc) le même nom que celui du fichier source du module (.ixx) que vous compilez. Dans l’exemple, le fichier ifc généré est std.ifc par défaut, car nous compilons le fichier de module std.ixx.
  4. Importez la bibliothèque std que vous avez générée en créant d’abord un fichier nommé importExample.cpp avec le contenu suivant :

    // 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;
        }
    }
    

    Dans le code précédent, import std; remplace #include <vector> et #include <iostream>. L’instruction import std; rend toutes les bibliothèques standard disponibles avec une seule instruction. L’importation de l’ensemble de la bibliothèque standard est souvent beaucoup plus rapide que le traitement d’un seul fichier d’en-tête de bibliothèque standard, comme #include <vector>.

  5. Compilez l’exemple en utilisant la commande suivante dans le même répertoire que celui de l’étape précédente :

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

    Il n’est pas nécessaire de spécifier /reference "std=std.ifc" sur la ligne de commande dans cet exemple, car le compilateur recherche automatiquement le fichier .ifc correspondant au nom du module spécifié par l’instruction import. Quand le compilateur rencontre import std;, il peut trouver std.ifc s’il se trouve dans le même répertoire que le code source. Si le fichier .ifc se trouve dans un répertoire différent du code source, utilisez le commutateur /reference du compilateur pour y faire référence.

    Dans cet exemple, la compilation du code source et la liaison de l’implémentation du module dans l’application sont des étapes distinctes. Ce n’est pas obligatoire. Vous pourriez utiliser cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj pour compiler et lier en une seule étape. Il peut cependant être pratique de générer et de lier séparément, car vous ne devez alors générer qu’une seule fois le module nommé de la bibliothèque standard, puis vous pouvez y faire référence depuis votre projet ou depuis plusieurs projets dans l’étape de liaison de votre build.

    Si vous générez un seul projet, vous pouvez combiner les étapes de génération du module nommé de bibliothèque standard std et l’étape de génération de votre application en ajoutant "%VCToolsInstallDir%\modules\std.ixx" à la ligne de commande. Placez-le avant les fichiers .cpp qui consomment le module std.

    Par défaut, le nom de l’exécutable en sortie est tiré du premier fichier en entrée. Utilisez l’option de compilateur /Fe pour spécifier le nom du fichier exécutable souhaité. Ce tutoriel montre la compilation du module nommé std dans une étape distincte, car vous ne devez générer le module nommé de bibliothèque standard qu’une seule fois, ensuite de quoi vous pouvez y faire référence depuis votre projet ou depuis plusieurs projets. Il peut cependant être pratique de tout générer ensemble, comme le fait cette ligne de commande :

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

    Avec la ligne de commande précédente, le compilateur produit un exécutable nommé importExample.exe. Quand vous l’exécutez, il génère la sortie suivante :

    Import the STL library for best performance
    555
    

Importer la bibliothèque standard et les fonctions C globales avec std.compat

La bibliothèque standard C++ inclut la bibliothèque standard C ISO. Le module std.compat fournit toutes les fonctionnalités du module std, comme std::vector, std::cout, std::printf, std::scanf, etc. Mais il fournit également les versions de l’espace de noms global de ces fonctions, comme ::printf, ::scanf, ::fopen, ::size_t, etc.

Le module nommé std.compat est une couche de compatibilité fournie pour faciliter la migration du code existant qui fait référence à des fonctions du runtime C dans l’espace de noms global. Si vous voulez éviter d’ajouter des noms à l’espace de noms global, utilisez import std;. Si vous devez faciliter la migration d’un codebase qui utilise de nombreuses fonctions non qualifiées du runtime C (espace de noms global), utilisez import std.compat;. Ceci fournit les noms du runtime C de l’espace de noms global, ce qui vous permet de ne pas devoir qualifier tous les noms globaux avec std::. Si vous n’avez pas de code existant qui utilise les fonctions du runtime C de l’espace de noms global, vous n’avez pas besoin d’utiliser import std.compat;. Si vous appelez seulement quelques fonctions du runtime C dans votre code, il peut être préférable d’utiliser import std; et de qualifier les quelques noms du runtime C de l’espace de noms global qui en ont besoin avec std::. Par exemple : std::printf(). Si vous voyez une erreur comme error C3861: 'printf': identifier not found quand vous essayez de compiler votre code, envisagez d’utiliser import std.compat; pour importer les fonctions du runtime C de l’espace de noms global.

Exemple : Comment générer et importer std.compat

Avant de pouvoir utiliser import std.compat;, vous devez compiler le fichier d’interface du module trouvé sous forme de code source dans std.compat.ixx. Visual Studio fournit le code source du module pour vous permettre de le compiler en utilisant les paramètres du compilateur qui correspondent à votre projet. Les étapes sont similaires à celles pour la génération du module nommé std. Le module nommé std est généré en premier, car std.compat dépend de lui :

  1. Ouvrez une invite de commandes des outils natifs pour VS : dans le menu Démarrer de Windows, tapez x86 natif : l’invite doit apparaître dans la liste des applications. Vérifiez que l’invite concerne Visual Studio 2022 version 17.5 ou ultérieure. Vous recevrez des erreurs du compilateur si vous utilisez la version incorrecte de l’invite.

  2. Créez un répertoire pour essayer cet exemple, comme %USERPROFILE%\source\repos\STLModules, et faites-en le répertoire actif. Si vous choisissez un répertoire auquel vous n’avez pas accès en écriture, vous recevrez des erreurs.

  3. Compilez les modules nommés std et std.compat avec la commande suivante :

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

    Vous devez compiler std et std.compat en utilisant les mêmes paramètres de compilateur que ceux que vous envisagez d’utiliser avec le code qui les importera. Si vous avez une solution multiprojet, vous pouvez les compiler une seule fois, puis y faire référence depuis tous vos projets en utilisant l’option de compilateur /reference.

    Si vous recevez des erreurs, vérifiez que vous utilisez la version correcte de l’invite de commandes.

    Le compilateur génère quatre fichiers au cours des deux étapes précédentes :

    • std.ifc est l’interface du module nommé binaire compilé que le compilateur consulte pour traiter l’instruction import std;. Le compilateur consulte également std.ifc pour traiter import std.compat;, car std.compat s’appuie sur std. C’est un artefact uniquement au moment de la compilation. Il n’est pas fourni avec votre application.
    • std.obj contient l’implémentation de la bibliothèque standard.
    • std.compat.ifc est l’interface du module nommé binaire compilé que le compilateur consulte pour traiter l’instruction import std.compat;. C’est un artefact uniquement au moment de la compilation. Il n’est pas fourni avec votre application.
    • std.compat.obj contient l’implémentation. Cependant, la plus grande partie de l’implémentation est fournie par std.obj. Ajoutez std.obj à la ligne de commande quand vous compilez l’exemple d’application pour lier statiquement les fonctionnalités de la bibliothèque standard que vous utilisez dans votre application.

    Vous pouvez contrôler le nom du fichier objet et le nom du fichier d’interface du module nommé avec les commutateurs suivants :

    • /Fo définit le nom du fichier objet. Par exemple : /Fo:"somethingelse". Par défaut, le compilateur utilise pour le fichier objet le même nom que le fichier source du module (.ixx) que vous compilez. Dans l’exemple, les noms des fichiers objet sont par défaut std.obj et std.compat.obj, car nous compilons les fichiers de module std.ixx et std.compat.obj.
    • /ifcOutput définit le nom du fichier d’interface du module nommé (.ifc). Par exemple : /ifcOutput "somethingelse.ifc". Par défaut, le compilateur utilise pour le fichier d’interface du module (.ifc) le même nom que celui du fichier source du module (.ixx) que vous compilez. Dans l’exemple, les fichiers ifc générés sont std.ifc et std.compat.ifc par défaut, car nous compilons les fichiers de module std.ixx et std.compat.ixx.
  4. Importez la bibliothèque std.compat en créant d’abord un fichier nommé stdCompatExample.cpp avec le contenu suivant :

    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);
        }
    }
    

    Dans le code précédent, import std.compat; remplace #include <cstdio> et #include <vector>. L’instruction import std.compat; rend la bibliothèque standard et les fonctions du runtime C disponibles avec une seule instruction. L’importation de ce module nommé, qui inclut la bibliothèque standard C++ et les fonctions de l’espace de noms global de la bibliothèque du runtime C, est plus rapide que le traitement d’un seul #include comme #include <vector>.

  5. Compilez l’exemple en utilisant la commande suivante :

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

    Nous n’avons pas dû spécifier std.compat.ifc sur la ligne de commande, car le compilateur recherche automatiquement le fichier .ifc qui correspond au nom du module dans une instruction import. Quand le compilateur rencontre import std.compat;, il trouve std.compat.ifc, car nous l’avons placé dans le même répertoire que le code source, ce qui nous évite de devoir le spécifier sur la ligne de commande. Si le fichier .ifc se trouve dans un répertoire différent de celui du code source ou qu’il a un nom différent, utilisez le commutateur du compilateur /reference pour y faire référence.

    Quand vous importez std.compat, vous devez établir un lien avec std.compat et avec std.obj, car std.compat utilise du code dans std.obj.

    Si vous créez un seul projet, vous pouvez combiner les étapes de génération des modules nommés de la bibliothèque standard std et std.compat en ajoutant "%VCToolsInstallDir%\modules\std.ixx" et "%VCToolsInstallDir%\modules\std.compat.ixx" (dans cet ordre) à la ligne de commande. Ce tutoriel montre la génération des modules de la bibliothèque standard comme une étape distincte, car vous ne devez générer qu’une seule fois les modules nommés de la bibliothèque standard, ensuite de quoi vous pouvez y faire référence depuis votre projet ou depuis plusieurs projets. Cependant, s’il est pratique de les générer tous en même temps, veillez à les placer avant les fichiers .cpp qui les consomment, et spécifiez /Fe pour nommer les fichiers exe générés comme indiqué dans cet exemple :

    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
    

    Dans cet exemple, la compilation du code source et la liaison de l’implémentation du module dans votre application sont des étapes distinctes. Ce n’est pas obligatoire. Vous pourriez utiliser cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.obj pour compiler et lier en une seule étape. Il peut cependant être pratique de générer et de lier séparément, car vous ne devez alors générer qu’une seule fois les modules nommés de la bibliothèque standard, puis vous pouvez y faire référence depuis votre projet ou depuis plusieurs projets dans l’étape de liaison de votre build.

    La commande du compilateur précédente produit un exécutable nommé stdCompatExample.exe. Quand vous l’exécutez, il génère la sortie suivante :

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

Considérations relatives aux modules nommés de la bibliothèque standard

Le contrôle de version pour les modules nommés est le même que pour les en-têtes. Les fichiers du module nommé .ixx sont installés au côté des en-têtes, par exemple : "%VCToolsInstallDir%\modules\std.ixx, qui se résout en C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixx dans la version des outils utilisés au moment de la rédaction de cette documentation. Sélectionnez la version du module nommé de la même façon que vous choisissez la version du fichier d’en-tête à utiliser : via le répertoire à partir duquel vous y faites référence.

Ne mélangez pas et ne mettez pas en correspondance l’importation d’unités d’en-tête et de modules nommés. Par exemple, n’utilisez pas import <vector>; et import std; dans le même fichier.

Ne mélangez pas et ne mettez pas en correspondance l’importation de fichiers d’en-tête de la bibliothèque standard C++ et les modules nommés std ou std.compat. Par exemple, n’utilisez pas #include <vector> et import std; dans le même fichier. Cependant, vous pouvez inclure des en-têtes C et importer des modules nommés dans le même fichier. Par exemple, vous pouvez utiliser import std; et #include <math.h> dans le même fichier. Simplement, n’incluez pas la version de la bibliothèque standard C++ <cmath>.

Vous n’avez pas à vous défendre contre l’importation d’un module plusieurs fois. Autrement dit, vous n’avez pas besoin de protections des en-têtes de style #ifndef dans les modules. Le compilateur sait quand il a déjà importé un module nommé et ignore les autres tentatives de le faire à nouveau.

Si vous devez utiliser la macro assert(), utilisez #include <assert.h>.

Si vous devez utiliser la macro errno, utilisez #include <errno.h>. Comme les modules nommés n’exposent pas de macros, c’est la solution de contournement si vous devez par exemple vérifier des erreurs de <math.h>.

Les macros comme NAN, INFINITY et INT_MIN sont définies par <limits.h>, que vous pouvez inclure. Cependant, si vous utilisez import std;, vous pouvez utiliser numeric_limits<double>::quiet_NaN() et numeric_limits<double>::infinity() à la place de NAN et INFINITY, et std::numeric_limits<int>::min() à la place de INT_MIN.

Résumé

Dans ce tutoriel, vous avez importé la bibliothèque standard en utilisant des modules. Découvrez ensuite comment créer et importer vos propres modules dans le tutoriel sur les modules nommés en C++.

Voir aussi

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++
Visite guidée des modules C++ dans Visual Studio
Modification d’un projet pour utiliser des modules nommés C++