cpp-modulos

Qué son y cómo usar módulos en C++

Los módulos ofrecen una alternativa más sencilla, eficiente y segura frente al sistema tradicional de inclusión de archivos mediante el preprocesador.

Tradicionalmente el código se en C++ se ha organizado en ficheros de cabecera .h y ficheros de implementación .cpp. Pero esto, francamente, siempre ha sido un engorro heredado de C 🤷.

La introducción de módulos en C++11 y su formalización en C++20 busca cambiar por completo la forma en que se estructuran y gestionan los proyectos C++.

El concepto de módulo existe en muchos lenguajes, y se ha convertido en un estándar en programación. Supone una enorme mejora para C++.

¿Qué es un módulo?

En términos sencillos, un módulo en C++ es una unidad de código que se compone de una declaración y definición de funciones, clases, todo en el mismo fichero.

Características clave

  • Encapsulamiento: Los módulos permiten definir qué es accesible desde fuera (lo que se exporta) y qué es privado para el módulo (lo que se mantiene interno).
  • Optimización de la compilación: Los módulos permiten compilar una vez y reutilizar ese resultado, reduciendo los tiempos de compilación.
  • Eliminación de dependencias circulares: Los módulos permiten importar solo los componentes necesarios, evitando las dependencias innecesarias.

Si eres un programador habitual, a estas alturas deberías estar llorando de ilusión por ver todas estas ventajas (yo sigo llorando 😭)

Sintaxis Básica de los Módulos

Declarar un Módulo

Un módulo en C++ se declara mediante la palabra clave module seguida del nombre del módulo.

La declaración se coloca generalmente en un archivo separado con extensión .cppm (o a veces .ixx, aunque .cppm es más común y estándar en la mayoría de los compiladores).

module mymodule;  // Declaramos un módulo llamado "mymodule"

export void foo() {  // Exportamos la función foo
    std::cout << "Hello from foo!" << std::endl;
}

export class MyClass {  // Exportamos una clase MyClass
public:
    void sayHello() {
        std::cout << "Hello from MyClass!" << std::endl;
    }
};

En este ejemplo:

  • module mymodule; declara el módulo mymodule.
  • Las funciones y clases que se prefijan con export se hacen accesibles desde fuera del módulo.

Importar un Módulo

Para usar un módulo en otro archivo, simplemente usamos la palabra clave import, que reemplaza al tradicional #include.

import mymodule;  // Importamos el módulo "mymodule"

int main() {
    foo();  // Llamamos a la función exportada desde mymodule
    MyClass obj;
    obj.sayHello();  // Usamos la clase exportada desde mymodule
}

En este ejemplo,

  • El archivo main.cpp importa el módulo mymodule y usa las entidades que han sido exportadas (la función foo y la clase MyClass).

Ejemplo con y sin módulos

Vamos a ver las ventajas de los módulos haciendo un ejemplo sencillo con el sistema tradicional de cabeceras, y con el nuevo sistema de módulos.

Ejemplo usando cabeceras

Primero veamos la opción tradicional, con ficheros de implementación y cabeceras. Tendríamos,

Este archivo incluye el encabezado y usa la función sum.

#include <iostream>
#include "sum.h"

int main() {
    int result = sum(3, 4);
    std::cout << "The sum is: " << result << std::endl;
    return 0;
}

Este archivo contiene la declaración de la función sum.

#ifndef SUM_H
#define SUM_H

int sum(int a, int b);

#endif

Este archivo define la función sum.

#include "sum.h"

int sum(int a, int b) {
    return a + b;
}

Para compilar este programa, normalmente se usaría un comando como:

g++ main.cpp sum.cpp -o program

Ejemplo usando módulos

Ahora implementamos el mismo ejemplo usando módulos. En lugar de tener un archivo de encabezado y un archivo de implementación separados, creamos un único archivo de módulo.

Este archivo contiene tanto la declaración como la definición de la función sum, y se exporta para que otros archivos puedan usarlo.

export module sum;

export int sum(int a, int b) {
    return a + b;
}

Para utilizar el módulo, simplemente lo importamos en el archivo main.cpp.

import sum;
#include <iostream>

int main() {
    int result = sum(3, 4);
    std::cout << "The sum is: " << result << std::endl;
    return 0;
}

Para compilar el programa con módulos, el comando sería algo similar a:

g++ -fmodules-ts sum.cppm main.cpp -o program

La opción -fmodules-ts se usa en algunos compiladores como GCC para habilitar los módulos de C++20, aunque las opciones pueden variar según el compilador.

Beneficios de los Módulos en C++

En gran medida los módulos de C++ están pensados para sustituir el uso de los archivos de encabezado en el lenguaje.

Los módulos introducen una forma más moderna, eficiente y segura de organizar y reutilizar código, solucionando varios problemas asociados con el sistema de encabezados tradicionales.

Por si aún no os he convencido, vamos a ver algunas de sus ventajas 👇