Espacios de nombres
Variantes
Acciones

Módulos (desde C++20)

De cppreference.com
< cpp‎ | language

La mayoría de los proyectos de C++ usan varias unidades de traducción, por lo que necesitan compartir declaraciones y definiciones entre esas unidades. El uso de archivos de encabezado es lo más usual para este propósito, siendo un ejemplo la biblioteca estándar cuyas declaraciones se proporciona al incluir ( #include ) el encabezado correspondiente.

Los módulos son una característica del lenguaje para compartir declaraciones y definiciones entre unidades de traducción. Son una alternativa a algunos casos de uso de archivos de encabezado.

Los módulos son ortogonales a los espacios de nombres.

// holamundo.cpp
export module holamundo;  // declaración de módulo
import <iostream>;         // importar declaración
 
export void hola() {      // exportar declaración
    std::cout << "Hola mundo!\n";
}
// main.cpp
import holamundo;  // importar declaración
 
int main() {
    hola();
}

Contenido

[editar] Sintaxis

export(opcional) module nombre-de-módulo partición-de-módulo(opcional) atributos(opcional) ; (1)
export declaración (2)
export { secuencia-declaraciones(opcional) } (3)
export(opcional) import nombre-de-módulo atributos(opcional) ; (4)
export(opcional) import partición-de-módulo atributos(opcional) ; (5)
export(opcional) import nombre-encabezado atributos(opcional) ; (6)
module ; (7)
module : private ; (8)
1) Declaración de módulo. Declara que la unidad de traducción actual es una unidad de módulo.
2,3) Exportar declaración. Exportar todas las declaraciones en el ámbito de espacio de nombres en declaración o secuencia-declaraciones.
4,5,6) Importar declaración. Importar una unidad de módulo/partición de módulo/unidad de encabezado.
7) Comienza un fragmento de módulo global.
8) Comienza un fragmento de módulo privado.

[editar] Declaraciones de módulos

Las unidades de traducción pueden tener una declaración de módulo, en cuyo caso se consideran unidad de módulo. La declaración de módulo, si se proporciona, debe ser la primera declaración de la unidad de traducción (exceptuando el fragmento de módulo global, que se tratará más adelante). Cada unidad de módulo está asociada a un nombre de módulo (y opcionalmente a una partición), proporcionado en la declaración del módulo.

export(opcional) module nombre-de-módulo partición-de-módulo(opcional) atributos(opcional) ;

El nombre del módulo consiste en uno o más identificadores separados por puntos (por ejemplo: mimodulo, mimodulo.misubmodulo, mimodulo2...). Los puntos no tienen significado en sí mismo, sin embargo, se suelen usar para representar jerarquía.

Un módulo con nombre es la colección de unidades de módulo con el mismo nombre de módulo.

Las unidades de módulo cuya declaración tiene la palabra clave export son unidades de interfaz de módulo. Otras unidades de módulo se llaman unidades de implementación de módulo.

Por cada módulo con nombre, debe haber exactamente una unidad de interfaz de módulo sin partición de módulo especificada. Esta unidad de módulo se denomina unidad de interfaz de módulo principal. Su contenido exportado estará disponible cuando se importe el módulo con nombre correspondiente.

// (cada línea representa una unidad de traducción separada)
 
export module A;   // declara la unidad de interfaz de módulo principal para módulo con nombre 'A'
module A;          // declara una unidad de implementación de módulo para el módulo con nombre 'A'
module A;          // declara otra unidad de implementación de módulo para el módulo con nombre  'A'
export module A.B; // declara la unidad de interfaz de módulo principal para el módulo con nombre 'A.B'
module A.B;        // declara una unidad de implementación de módulo para el módulo con nombre 'A.B'

[editar] Exportación de declaraciones y definiciones

Las unidades de interfaz de módulo pueden exportar declaraciones y definiciones, que se pueden importar por otras unidades de traducción. Pueden tener como prefijo la palabra clave export, o estar dentro de un bloque export.

export declaración
export { secuencia-de-declaraciones(opcional) }
export module A;   // declara la unidad de interfaz de módulo principal para el módulo con nombre 'A'
 
// hola() será visible para las unidades de traducción que importen 'A'
export char const* hola() { return "hola"; } 
 
// mundo() NO será visible.
char const* mundo() { return "mundo"; }
 
// uno() y cero() serán visibles.
export {
    int uno()  { return 1; }
    int cero() { return 0; }
}
 
// La exportación de espacios de nombres también funciona: hi::ingles() y hi::frances() serán visibles.
export namespace hi {
    char const* ingles() { return "Hi!"; }
    char const* frances()  { return "Salut!"; }
}

[editar] Importación de módulos y encabezados

Se pueden importar los módulos mediante una declaración de importación:

export(opcional) import nombre-de-módulo atributos(opcional) ;

Todas las declaraciones y definiciones exportadas en las unidades de interfaz de módulo del módulo con el nombre dado estarán disponibles en la unidad de traducción mediante la declaración de importación.

Las declaraciones de importación se pueden exportar en una unidad de interfaz de módulo. Es decir, si el módulo A exporta-importa B, entonces al importar A también estarán visibles todas las exportaciones de B.

En las unidades de módulo, todas las declaraciones de importación (incluyendo exportación-importación) deben agruparse después de la declaración de módulo y antes que todas las demás declaraciones.

/////// A.cpp    (unidad de interfaz de módulo principal de 'A')
export module A;
 
export char const* hola() { return "hola"; }
 
/////// B.cpp    (unidad de interfaz de módulo principal de 'B')
export module B;
 
export import A;
 
export char const* mundo() { return "mundo"; }
 
/////// main.cpp (no una unidad de módulo)
#include <iostream>
import B;
 
int main() {
    std::cout << hola() << ' ' << mundo() << '\n';
}

No debe usarse #include en una unidad de módulo (fuera del fragmento de módulo global), porque todas las declaraciones y definiciones incluidas se considerarían parte del módulo. En cambio, los encabezados también se pueden importar mediante una declaración de importación:

export(opcional) import nombre-de-encabezado atributos(opcional) ;

Importar un archivo de encabezado hará accesible todas su declaraciones y definiciones. Las macros de preprocesador también son accesibles (debido a que el preprocesador reconoce las declaraciones de importación). Sin embrago, al contrario que #include, las macros de preprocesamiento definidas en la unidad de traducción no afectarán el proceso del archivo de encabezado. En algunos casos esto puede ser un inconveniente (algunos archivos de encabezado usan macros de preprocesamiento como una forma de configuración), en este caso de necesita el uso del fragmento de módulo global.

/////// A.cpp    (unidad de interfaz de módulo principal de 'A')
export module A;
 
import <iostream>;
export import <string_view>;
 
export void print(std::string_view mensaje) {
    std::cout << mensaje << std::endl;
}
 
/////// main.cpp (no una unidad de módulo)
import A;
 
int main() {
    std::string_view mensaje = "Hola, mundo";
    print(mensaje);
}

[editar] Fragmento de módulo global

Las unidades de módulo pueden estar precedidas por un fragmento de módulo global, que puede usarse para incluir archivos de encabezado cuando no es posible importarlos (especialmente cuando el archivo de encabezado usa marcos de preprocesamiento como configuración).

module;

directivas-de-preprocesamiento(opcional)

declaración-de-módulo

Si una unidad de módulo tiene un fragmento de módulo global, su primera declaración debe ser {ttb|module;}}. Después, solamente pueden aparecer directivas de preprocesamiento en el fragmento de módulo global. A continuación, una declaración de módulo estándar señala el final del fragmento de módulo global y el comienzo del contenido del módulo.

/////// A.cpp    (unidad de interfaz de módulo principal de 'A')
module;
 
// La definición de _POSIX_C_SOURCE añade funciones a los encabezados estándar,
// según el estándar POSIX.
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
 
export module A;
 
import <ctime>;
 
// Solo para demostración (mala fuente de aleatoriedad). Usa C++ <random> en su lugar.
export double weak_random() {
    std::timespec ts;
    std::timespec_get(&ts, TIME_UTC); // de <ctime>
    // Proporcionado en <stdlib.h> de acuerdo con el estándar POSIX.
    srand48(ts.tv_nsec);
    // drand48() devuelve un número aleatorio entre 0 y 1.
    return drand48();
}
 
/////// main.cpp (no una unidad de módulo)
import <iostream>;
import A;
 
int main() {
    std::cout << "Valor aleatorio entre 0 y 1: " << weak_random() << '\n';
}

[editar] Fragmento de módulo privado

La unidad de interfaz de módulo principal puede estar seguida por un fragmento de módulo privado, que permite a un módulo se represente como una única unidad de traducción sin que todo el contenido este accesible a los importadores.

module : private ;

secuencia-de-declaraciones(opcional)

El fragmento de módulo privado finaliza la parte de la unidad de interfaz de módulo que puede afectar al comportamiento de otra unidades de traducción. Si una unidad de módulo contiene un fragmento de módulo privado, será la única unidad de módulo de su módulo.

export module foo;
export int f();
 
module :private; // finaliza la parte de la unidad de interfaz de módulo
                 // que puede afectar el comportamiento de otras unidades de traducción
                 // comienza un fragmento de módulo privado
 
int f() {        // definición no disponible para importadores de foo
    return 42;
}

[editar] Particiones de módulo

Un módulo puede tener unidades de partición de módulo. Son unidades de módulo cuyas declaraciones de módulo incluyen una partición de módulo, que comienza con dos puntos : y se pone después del nombre del módulo.

export module A:B; // Declara una unidad de interfaz de módulo para el módulo 'A', partición ':B'.

Una partición de módulo representa exactamente una unidad de módulo (dos unidades de módulo no pueden designar las misma partición de módulo). Solo son visibles desde el interior del módulo con nombre (las unidades de traducción fuera del módulo con nombre no pueden importar una partición de módulo directamente).

Se puede importar una partición de módulo por unidades de módulo del mismo módulo con nombre.

export(opcional) import partición-de-módulo atributos(opcional) ;
/////// A-B.cpp   
export module A:B;
...
 
/////// A-C.cpp
module A:C;
...
 
/////// A.cpp
export module A;
 
import :C;
export import :B;
 
...

Todas las definiciones y declaraciones en una partición de módulo son visibles para la unidad de módulo que la importa, ya sea exportada o no.

Las particiones de módulo pueden ser unidades de interfaz de módulo (cuando sus declaraciones de módulo tienen export). Deben ser exportados-importados por la unidad de interfaz de módulo principal, y sus declaraciones exportadas serán visibles cuando se importe el módulo.

export(opcional) import partición-de-módulo atributos(opcional) ;
///////  A.cpp   
export module A;   // unidad de interfaz de módulo principal
 
export import :B;     // Hola() es visible cuando se importa 'A'.
import :C;            // MundoImpl() ahora es visible solo para 'A.cpp'.
// export import :C;  // ERROR: No se puede exportar una unidad de implementación de módulo.
 
// Mundo() es visible para cualquier unidad de traducción que importe 'A'.
export char const* Mundo) {
    return MundoImpl();
}
/////// A-B.cpp 
export module A:B; // partición de unidad de interfaz de módulo
 
// Hola() es visible para cualquier unidad de traducción que importe 'A'.
export char const* Hola() { return "Hola"; }
/////// A-C.cpp 
module A:C;        // partición de unidad de implementación de módulo
 
// MundoImpl() es visible por cualquier unidad de módulo de 'A' que importe ':C'.
char const* MundoImpl() { return "Mundo"; }
/////// main.cpp 
import A;
import <iostream>;
 
int main() {
    std::cout << Hola) << ' ' << Mundo() << '\n';
    // MundoImpl(); // ERROR: MundoImpl() no es visible.
}

[editar] Palabras clave

export, import, module