Share via


Tutorial: importación de la biblioteca estándar de C++ mediante módulos desde la línea de comandos

Obtenga información sobre cómo importar la biblioteca estándar de C++ mediante módulos de biblioteca de C++. Esto permite una compilación más rápida y es más sólida que usar archivos de encabezado o unidades de encabezado o encabezados precompilados (PCH).

En este tutorial, obtendrá información sobre:

  • Cómo importar la biblioteca estándar como un módulo desde la línea de comandos.
  • Ventajas de rendimiento y uso de los módulos.
  • Los dos módulos de biblioteca estándar std y std.compat y la diferencia entre ellos.

Requisitos previos

Este tutorial requiere Visual Studio 2022 17.5 o posterior.

Introducción a los módulos de biblioteca estándar

Los archivos de encabezado presentan una semántica que puede cambiar en función de las definiciones de macros y del orden en que se incluyan, y ralentizan la compilación. Los módulos resuelven estos problemas.

Ahora es posible importar la biblioteca estándar como un módulo en lugar de como una maraña de archivos de encabezado. Esto es mucho más rápido y sólido que incluir archivos de encabezado o unidades de encabezado o encabezados precompilados (PCH).

La biblioteca estándar de C++23 presenta dos módulos con nombre: std y std.compat:

  • std exporta las declaraciones y los nombres definidos en el espacio de nombres de la biblioteca estándar de C++ std, como std::vector. También exporta el contenido de los encabezados de contenedor de C, como <cstdio> y <cstdlib>, que proporcionan funciones como std::printf(). Las funciones de C definidas en el espacio de nombres global, como ::printf(), no se exportan. Esto mejora la situación en la que incluir un encabezado contenedor de C como <cstdio>también incluye archivos de encabezado de C como stdio.h, que aportan las versiones del espacio de nombres global de C. Esto no es un problema si importa std.
  • std.compat exporta todo lo que hay en std y agrega los espacios de nombres globales del entorno de ejecución de C, como ::printf, ::fopen, ::size_t, ::strlen, etc. El módulo std.compat facilita el trabajo con códigos base que hacen referencia a muchas funciones o tipos en runtime de C en el espacio de nombres global.

El compilador importa toda la biblioteca estándar cuando se usa import std; o import std.compat; y lo hace más rápido que incorporando un único archivo de encabezado. Es más rápido incorporar toda la biblioteca estándar con import std; (o import std.compat) que con #include <vector>, por ejemplo.

Dado que los módulos con nombre no exponen macros, las macros como assert, errnooffsetof, va_arg y otras no están disponibles al importar std o std.compat. Consulte Consideraciones sobre módulos con nombre de biblioteca estándar para obtener soluciones alternativas.

Acerca de los módulos de C++

Los archivos de encabezado indican cómo se han compartido las declaraciones y las definiciones entre archivos de código fuente en C++. Antes de los módulos de biblioteca estándar, incluiría la parte de la biblioteca estándar que necesitaba con una directiva como #include <vector>. Los archivos de encabezado son frágiles y difíciles de componer porque su semántica puede cambiar en función del orden en que los incluya o si se definen determinadas macros. También ralentizan la compilación porque cada archivo de origen los vuelve a procesar.

C++20 presenta una alternativa moderna denominada módulos. En C++23, pudimos poner en mayúscula la compatibilidad con módulos para introducir módulos con nombre para representar la biblioteca estándar.

Al igual que los archivos de encabezado, los módulos permiten compartir declaraciones y definiciones entre archivos de código fuente. Pero a diferencia de los archivos de encabezado, los módulos no son frágiles y son más fáciles de componer porque su semántica no cambia debido a definiciones de macros o al orden en el que se importan. El compilador puede procesar módulos mucho más rápido de lo que puede procesar archivos #include y también usa menos memoria en tiempo de compilación. Los módulos con nombre no exponen definiciones de macro ni detalles de implementación privada.

Para obtener información detallada sobre los módulos, consulte Introducción a los módulos en C++ En este artículo también se describe el consumo de la biblioteca estándar de C++ como módulos, pero se usa una forma más antigua y experimental de hacerlo.

En este artículo se muestra la nueva y mejor manera de consumir la biblioteca estándar. Para obtener más información sobre las formas alternativas de consumir la biblioteca estándar, consulte Comparación de unidades de encabezado, módulos y encabezados precompilados.

Importación de la biblioteca estándar con std

En los siguientes ejemplos se muestra cómo consumir la biblioteca estándar como módulo mediante el compilador de línea de comandos. Para obtener información sobre cómo hacerlo en el IDE de Visual Studio, consulte Compilación de módulos de biblioteca estándar ISO C++23.

La instrucción import std; o import std.compat; importa la biblioteca estándar en la aplicación. Pero en primer lugar, debe compilar los módulos con nombre de la biblioteca estándar en forma binaria. Los siguientes pasos muestran cómo hacerlo.

Ejemplo: cómo compilar e importar std

  1. Abra un símbolo del sistema de las herramientas nativas x86 para VS: en el menú Inicio de Windows, escriba x86 nativo y el símbolo del sistema debería aparecer en la lista de aplicaciones. Asegúrese de que la solicitud es para Visual Studio 2022, versión 17.5 o posterior. Si usa la versión incorrecta del símbolo del sistema, obtendrá errores. Los ejemplos usados en este tutorial son para el shell de CMD.

  2. Cree un directorio, como %USERPROFILE%\source\repos\STLModules, y conviértalo en el directorio actual. Si elige un directorio al que no tiene acceso de escritura, obtendrá errores durante la compilación.

  3. Compile el módulo std con nombre con el siguiente comando:

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

    Si recibe errores, asegúrese de que usa la versión correcta del símbolo del sistema.

    Compile el módulo std con nombre con la misma configuración del compilador que quiere usar con el código que importa el módulo compilado. Si tiene una solución de varios proyectos, puede compilar el módulo con nombre de biblioteca estándar una vez, y luego hacer referencia a él desde todos sus proyectos utilizando la opción /reference del compilador.

    Con el comando anterior del compilador, este genera dos archivos:

    • std.ifc es la representación binaria compilada de la interfaz del módulo con nombre que el compilador consulta para procesar la instrucción import std;. Se trata de un cambio solo en tiempo de compilación. No se envía con la aplicación.
    • std.obj contiene la implementación del módulo con nombre. Agregue std.obj a la línea de comandos al compilar la aplicación de ejemplo para vincular estáticamente la funcionalidad que usa desde la biblioteca estándar a la aplicación.

    Los modificadores de la línea de comandos clave de este ejemplo son:

    Modificador Significado
    /std:c++:latest Use la versión más reciente del estándar y la biblioteca del lenguaje C++. Aunque la compatibilidad con módulos está disponible en /std:c++20, necesita la biblioteca estándar más reciente para obtener compatibilidad para los módulos con nombre de biblioteca estándar.
    /EHsc Use el control de excepciones de C++, excepto para las funciones marcadas como extern "C".
    /W4 Por lo general, se recomienda usar /W4, especialmente en los nuevos proyectos, ya que habilita todas las advertencias de nivel 1, nivel 2, nivel 3 y la mayoría de los niveles 4 (informativos), lo que puede ayudar a detectar posibles problemas al principio. Básicamente proporciona advertencias similares a lint que pueden ayudar a garantizar los defectos de código más difíciles de encontrar.
    /c Compile sin vincular, ya que solo estamos creando la interfaz de módulo con nombre binario en este momento.

    Puede controlar el nombre del archivo de objeto y el nombre del archivo de interfaz de módulo con los siguientes modificadores:

    • /Fo establece el nombre del archivo de objeto. Por ejemplo, /Fo:"somethingelse". De forma predeterminada, el compilador usa el mismo nombre para el archivo de objeto que el archivo de origen del módulo (.ixx) que está compilando. En el ejemplo, el nombre del archivo de objeto es std.obj de forma predeterminada porque estamos compilando el archivo de módulo std.ixx.
    • /ifcOutput establece el nombre del archivo de interfaz del módulo con nombre (.ifc). Por ejemplo, /ifcOutput "somethingelse.ifc". De forma predeterminada, el compilador usa el mismo nombre para el archivo de interfaz del módulo (.ifc) que el archivo de origen del módulo (.ixx) que está compilando. En el ejemplo, el archivo ifc generado es std.ifc de forma predeterminada porque estamos compilando el archivo de módulo std.ixx.
  4. Importe la biblioteca std que compiló creando primero un archivo con nombre importExample.cpp con el siguiente contenido:

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

    En el código anterior, import std; reemplaza #include <vector> y #include <iostream>. La instrucción import std; hace que toda la biblioteca estándar esté disponible con una instrucción. La importación de toda la biblioteca estándar suele ser mucho más rápida que procesar un único archivo de encabezado de biblioteca estándar, como #include <vector>.

  5. Compile el ejemplo mediante el siguiente comando en el mismo directorio que el paso anterior:

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

    No es necesario especificar /reference "std=std.ifc" en la línea de comandos de este ejemplo porque el compilador busca automáticamente el archivo .ifc que coincide con el nombre del módulo especificado por la instrucción import. Cuando el compilador encuentra import std;, puede encontrar std.ifc si se encuentra en el mismo directorio que el código fuente. Si el archivo .ifc está en un directorio diferente al código fuente, use el modificador /reference del compilador para hacer referencia a él.

    En este ejemplo, la compilación del código fuente y la vinculación de la implementación del módulo a la aplicación son pasos independientes. No tienen que serlo. Puede usar cl /std:c++latest /EHsc /nologo /W4 /reference "std=std.ifc" importExample.cpp std.obj para compilar y vincular en un mismo paso. Pero puede ser conveniente compilar y vincular por separado porque solo necesita compilar el módulo con nombre de biblioteca estándar una vez y, a continuación, puede hacer referencia a él desde el proyecto, o desde varios proyectos, en el paso de vínculo de la compilación.

    Si va a compilar un solo proyecto, puede combinar los pasos de creación del módulo con nombre de biblioteca estándar std y el paso de compilar la aplicación agregando "%VCToolsInstallDir%\modules\std.ixx" a la línea de comandos. Colóquelo antes de los archivos .cpp que consuman el módulo std.

    De forma predeterminada, el nombre del ejecutable de salida se toma del primer archivo de entrada. Use la opción /Fe del compilador para especificar el nombre de archivo ejecutable que desee. En este tutorial se muestra la compilación del módulo con nombre como paso independiente porque solo necesita compilar el módulo con nombre de biblioteca estándar una vez y, a continuación, puede hacer referencia a él desde el proyecto std o desde varios proyectos. Pero puede ser conveniente compilar todo junto, como se muestra en esta línea de comandos:

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

    Dada la línea de comandos anterior, el compilador genera un archivo ejecutable con nombre importExample.exe. Al ejecutarlo, genera el siguiente resultado:

    Import the STL library for best performance
    555
    

Importación de la biblioteca estándar y las funciones globales de C con std.compat

La biblioteca estándar de C++ incluye la biblioteca estándar ISO C. El módulo std.compat proporciona toda la funcionalidad del módulo std, como std::vector, std::cout, std::printf, std::scanf, etc. Pero también proporciona las versiones de espacio de nombres globales de estas funciones, como ::printf, ::scanf, ::fopen, ::size_t, etc.

El módulo std.compat con nombre es una capa de compatibilidad proporcionada para facilitar la migración del código existente que hace referencia a las funciones en runtime de C en el espacio de nombres global. Si desea evitar agregar nombres al espacio de nombres global, use import std;. Si necesita facilitar la migración de un código base que usa muchas funciones en runtime de C sin calificar (espacio de nombres global), use import std.compat;. Esto proporciona los nombres de runtime de C del espacio de nombres global para que no tenga que calificar todos los nombres globales con std::. Si no tiene ningún código existente que use las funciones de runtime del espacio de nombres global de C, no es necesario usar import std.compat;. Si solo llama a algunas funciones en runtime de C en el código, puede ser mejor usar import std; y calificar los pocos nombres de runtime de espacio de nombres globales de C que lo necesitan con std::. Por ejemplo, std::printf(). Si ve un error similar a error C3861: 'printf': identifier not found al intentar compilar el código, considere la posibilidad de usar import std.compat; para importar las funciones en runtime del espacio de nombres global de C.

Ejemplo: cómo compilar e importar std.compat

Para poder usar import std.compat;, debe compilar el archivo de interfaz de módulo que se encuentra en el formulario de código fuente en std.compat.ixx. Visual Studio incluye el código fuente del módulo para que pueda compilar el módulo mediante la configuración del compilador que coincida con el proyecto. Los pasos son similares a para compilar el módulo std con nombre. El módulo std con nombre se compila primero porque std.compat depende de él:

  1. Abra un símbolo del sistema de herramientas nativas para VS: en el menú Inicio de Windows, escriba x86 nativo y el símbolo del sistema debería aparecer en la lista de aplicaciones. Asegúrese de que la solicitud es para Visual Studio 2022, versión 17.5 o posterior. Si usa la versión incorrecta del símbolo del sistema, obtendrá errores del compilador.

  2. Cree un directorio para probar este ejemplo, como %USERPROFILE%\source\repos\STLModules, y conviértalo en el directorio actual. Si elige un directorio al que no tiene acceso de escritura, obtendrá errores.

  3. Compile los módulos con nombre std y std.compat con el siguiente comando:

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

    Debe compilar std y std.compat con la misma configuración del compilador que pretende usar con el código que los importará. Si tiene una solución de varios proyectos, puede compilarlos una vez y, a continuación, hacer referencia a ellos desde todos los proyectos mediante la opción /reference del compilador.

    Si recibe errores, asegúrese de que usa la versión correcta del símbolo del sistema.

    El compilador genera cuatro archivos de los dos pasos anteriores:

    • std.ifc es la interfaz de módulo con nombre binario compilado que el compilador consulta para procesar la instrucción import std;. El compilador también consulta std.ifc para procesar import std.compat; porque std.compat se basa en std. Se trata de un cambio solo en tiempo de compilación. No se envía con la aplicación.
    • std.obj contiene la implementación de la biblioteca estándar.
    • std.compat.ifc es la interfaz de módulo con nombre binario compilado que el compilador consulta para procesar la instrucción import std.compat;. Se trata de un cambio solo en tiempo de compilación. No se envía con la aplicación.
    • std.compat.obj contiene implementación. Sin embargo, la mayoría de la implementación la proporciona std.obj. Agregue std.obj a la línea de comandos al compilar la aplicación de ejemplo para vincular estáticamente la funcionalidad que usa desde la biblioteca estándar a la aplicación.

    Puede controlar el nombre del archivo de objeto y el nombre del archivo de interfaz de módulo con los siguientes modificadores:

    • /Fo establece el nombre del archivo de objeto. Por ejemplo, /Fo:"somethingelse". De forma predeterminada, el compilador usa el mismo nombre para el archivo de objeto que el archivo de origen del módulo (.ixx) que está compilando. En el ejemplo, los nombres de archivo de objeto son std.obj y std.compat.obj de forma predeterminada porque estamos compilando los archivos de módulo std.ixx y std.compat.obj.
    • /ifcOutput establece el nombre del archivo de interfaz del módulo con nombre (.ifc). Por ejemplo, /ifcOutput "somethingelse.ifc". De forma predeterminada, el compilador usa el mismo nombre para el archivo de interfaz del módulo (.ifc) que el archivo de origen del módulo (.ixx) que está compilando. En el ejemplo, los archivos ifc generados son std.ifc y std.compat.ifc de forma predeterminada porque estamos compilando los archivos de módulo std.ixx y std.compat.ixx.
  4. Importe la biblioteca std.compat creando primero un archivo con nombre stdCompatExample.cpp con el siguiente contenido:

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

    En el código anterior, import std.compat; reemplaza #include <cstdio> y #include <vector>. La instrucción import std.compat; hace que la biblioteca estándar y las funciones en runtime de C estén disponibles con una instrucción. Importar este módulo con nombre, que incluye la biblioteca estándar de C++ y las funciones de espacio de nombres globales de la biblioteca en runtime de C, es más rápido que procesar un solo #include como #include <vector>.

  5. Para compilar el ejemplo, use el siguiente comando:

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

    No teníamos que especificar std.compat.ifc en la línea de comandos porque el compilador busca automáticamente el archivo .ifc que coincide con el nombre del módulo en una instrucción import. Cuando el compilador encuentra import std.compat;, encuentra std.compat.ifc, ya que lo colocamos en el mismo directorio que el código fuente, lo que nos libera de la necesidad de especificarlo en la línea de comandos. Si el archivo .ifc está en un directorio diferente del código fuente o tiene un nombre diferente, use el modificador del compilador /reference para hacer referencia a él.

    Al importar std.compat, debe vincular con std.compat y std.obj porque std.compat usa código en std.obj.

    Si va a compilar un solo proyecto, puede combinar los pasos de compilación del módulo con nombre de biblioteca std y std.compat agregando "%VCToolsInstallDir%\modules\std.ixx" y "%VCToolsInstallDir%\modules\std.compat.ixx" (en ese orden) a la línea de comandos. En este tutorial se muestra cómo compilar los módulos de biblioteca estándar como un paso independiente, ya que solo necesita compilar los módulos con nombre de biblioteca estándar una vez y, a continuación, puede hacer referencia a ellos desde su proyecto o desde varios proyectos. Pero si es conveniente compilarlos a la vez, asegúrese de colocarlos antes de los archivos .cpp que los consuman y especifique /Fe para asignar un nombre al compilado exe como se muestra en este ejemplo:

    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
    

    En este ejemplo, la compilación del código fuente y la vinculación de la implementación del módulo a su aplicación son pasos independientes. No tienen que serlo. Puede usar cl /std:c++latest /EHsc /nologo /W4 stdCompatExample.cpp std.obj std.compat.obj para compilar y vincular en un mismo paso. Pero puede ser conveniente compilar y vincular por separado porque solo necesita compilar los módulos con nombre de biblioteca estándar una vez y, a continuación, puede hacer referencia a ellos desde el proyecto, o desde varios proyectos, en el paso de vínculo de la compilación.

    El comando del compilador anterior genera un archivo ejecutable con nombre stdCompatExample.exe. Al ejecutarlo, genera el siguiente resultado:

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

Consideraciones sobre módulos con nombre de biblioteca estándar

El control de versiones para los módulos con nombre es el mismo que para los encabezados. Los archivos de módulo con nombre .ixx se instalan junto con los encabezados, por ejemplo: "%VCToolsInstallDir%\modules\std.ixx, que se resuelve en la versión C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Tools\MSVC\14.38.33130\modules\std.ixx de las herramientas usadas en el momento de redactar este documento. Seleccione la versión del módulo con nombre de la misma manera en que elija la versión del archivo de encabezado desde la que va a usar el directorio desde el que hace referencia.

No combine ni haga coincidir la importación de unidades de encabezado y los módulos con nombre. Por ejemplo, no import <vector>; y import std; en el mismo archivo.

No combine ni haga coincidir la importación de archivos de encabezado de biblioteca estándar de C++ y los módulos con nombre std o std.compat. Por ejemplo, no #include <vector> y import std; en el mismo archivo. Sin embargo, puede incluir encabezados de C e importar módulos con nombre en el mismo archivo. Por ejemplo, puede import std; y #include <math.h> en el mismo archivo. Simplemente no incluya la versión <cmath> de la biblioteca estándar de C++.

No tiene que defenderse de la importación de un módulo varias veces. Es decir, no necesita protección de encabezados de estilo #ifndef en módulos. El compilador sabe cuándo ya ha importado un módulo con nombre y omite los intentos duplicados.

Si necesita usar la macro assert(), entonces #include <assert.h>.

Si necesita usar la macro errno, #include <errno.h>. Dado que los módulos con nombre no exponen macros, esta es la solución alternativa si necesita comprobar si hay errores de <math.h>, por ejemplo.

Las macros como NAN, INFINITY y INT_MIN se definen mediante <limits.h>, que puede incluir. Sin embargo, si import std;, puede usar numeric_limits<double>::quiet_NaN() y numeric_limits<double>::infinity() en lugar de NAN y INFINITY, y std::numeric_limits<int>::min() en lugar de INT_MIN.

Resumen

En este tutorial, ha importado la biblioteca estándar mediante módulos. A continuación, obtenga información sobre cómo crear e importar sus propios módulos en Tutorial de módulos con nombre en C++.

Consulte también

Comparación de unidades de encabezado, módulos y encabezados precompilados
Información general de los módulos en C++
Un recorrido por los módulos de C++ en Visual Studio
Traslado de un proyecto a módulos con nombre de C++