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
ystd.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
, comostd::vector
. También exporta el contenido de los encabezados de contenedor de C, como<cstdio>
y<cstdlib>
, que proporcionan funciones comostd::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 comostdio.h
, que aportan las versiones del espacio de nombres global de C. Esto no es un problema si importastd
.std.compat
exporta todo lo que hay enstd
y agrega los espacios de nombres globales del entorno de ejecución de C, como::printf
,::fopen
,::size_t
,::strlen
, etc. El módulostd.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
, errno
offsetof
, 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
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.
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.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ónimport 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. Agreguestd.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 esstd.obj
de forma predeterminada porque estamos compilando el archivo de módulostd.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 archivoifc
generado esstd.ifc
de forma predeterminada porque estamos compilando el archivo de módulostd.ixx
.
Importe la biblioteca
std
que compiló creando primero un archivo con nombreimportExample.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ónimport 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>
.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ónimport
. Cuando el compilador encuentraimport std;
, puede encontrarstd.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ódulostd
.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 proyectostd
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:
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.
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.Compile los módulos con nombre
std
ystd.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
ystd.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ónimport std;
. El compilador también consultastd.ifc
para procesarimport std.compat;
porquestd.compat
se basa enstd
. 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ónimport 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 proporcionastd.obj
. Agreguestd.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 sonstd.obj
ystd.compat.obj
de forma predeterminada porque estamos compilando los archivos de módulostd.ixx
ystd.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 archivosifc
generados sonstd.ifc
ystd.compat.ifc
de forma predeterminada porque estamos compilando los archivos de módulostd.ixx
ystd.compat.ixx
.
Importe la biblioteca
std.compat
creando primero un archivo con nombrestdCompatExample.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ónimport 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>
.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ónimport
. Cuando el compilador encuentraimport std.compat;
, encuentrastd.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 constd.compat
ystd.obj
porquestd.compat
usa código enstd.obj
.Si va a compilar un solo proyecto, puede combinar los pasos de compilación del módulo con nombre de biblioteca
std
ystd.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 compiladoexe
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++