Modules de C++20
Aprende por que los modules reemplazan a los headers, como declarar y exportar modules, particiones de modules y la ruta de transicion desde codigo basado en headers.
Por Que los Modules Reemplazan a los Headers
El sistema #include ha sido la base de la compilacion de C++ durante decadas, pero conlleva problemas serios. Los headers se incluyen textualmente — el preprocesador copia el archivo completo en cada unidad de traduccion. Esto significa que #include podria pegar mas de 50,000 lineas de codigo en cada archivo .cpp. El resultado: compilacion lenta, orden de inclusion fragil (macros de un header afectando a otro) y violaciones de la Regla de Una Definicion (ODR) que causan comportamiento indefinido silencioso.
Los modules de C++20 resuelven todos estos problemas. Un module se compila una vez en una interfaz binaria, y luego es importado por los consumidores sin re-analisis. No hay inclusion textual, no hay fuga de macros y no hay dependencia del orden de inclusion. Los tiempos de compilacion pueden mejorar de 5 a 20 veces para proyectos grandes.
Declaracion y Exportacion de Modules
Un module tiene una unidad de interfaz (declarando lo que se exporta) y opcionalmente una o mas unidades de implementacion. La palabra clave export hace las declaraciones visibles para los importadores:
// math_utils.cppm — module interface unit
export module math_utils; // module declaration
import <cmath>; // import a header unit
// Exported — visible to importers
export double circle_area(double radius) {
return std::numbers::pi * radius * radius;
}
export struct Point {
double x, y;
double distance_to(const Point& other) const {
double dx = x - other.x;
double dy = y - other.y;
return std::sqrt(dx * dx + dy * dy);
}
};
// NOT exported — module-private (internal linkage)
double helper_function(double val) {
return val * val;
}
// Export a namespace — all members become visible
export namespace geometry {
double triangle_area(double base, double height) {
return 0.5 * base * height;
}
double rectangle_area(double width, double height) {
return width * height;
}
}Importando Modules
Los consumidores usan import para acceder a las declaraciones exportadas de un module. A diferencia de #include, import no realiza inclusion textual — las macros y detalles de implementacion no se filtran a traves de los limites del module:
// main.cpp — imports the module
import math_utils;
#include <iostream> // can still use #include for non-modularized headers
int main() {
double area = circle_area(5.0);
std::cout << "Circle area: " << area << "\n";
Point p1{0.0, 0.0};
Point p2{3.0, 4.0};
std::cout << "Distance: " << p1.distance_to(p2) << "\n"; // 5.0
std::cout << "Triangle: " << geometry::triangle_area(10, 5) << "\n";
// helper_function(42); // ERROR: not exported, not visible
return 0;
}
// Compile order matters for modules:
// 1. Compile the module interface first:
// g++ -std=c++20 -fmodules-ts -c math_utils.cppm
// 2. Then compile the consumer:
// g++ -std=c++20 -fmodules-ts main.cpp -o mainParticiones de Modules
Los modules grandes pueden dividirse en particiones — sub-modules que juntos forman la interfaz completa del module. Las particiones son internas al module y no pueden ser importadas directamente por consumidores externos:
// --- math_utils-algebra.cppm (partition interface) ---
export module math_utils:algebra;
export double square(double x) { return x * x; }
export double cube(double x) { return x * x * x; }
// --- math_utils-trig.cppm (partition interface) ---
export module math_utils:trig;
import <cmath>;
export double deg_to_rad(double degrees) {
return degrees * std::numbers::pi / 180.0;
}
export double sin_deg(double degrees) {
return std::sin(deg_to_rad(degrees));
}
// --- math_utils.cppm (primary module interface) ---
export module math_utils;
// Re-export partitions — consumers see everything
export import :algebra;
export import :trig;
// Additional exports can go here
export double hypotenuse(double a, double b) {
return std::sqrt(square(a) + square(b));
}
// --- Consumer ---
// import math_utils; // gets algebra, trig, and hypotenuseimport std (C++23)
C++23 introduce import std; que importa toda la biblioteca estandar como un module. Esto es un cambio radical para la velocidad de compilacion:
import std; // Todo: iostream, vector, string, algorithm, ...
int main() {
std::println("Hello from C++23 modules!");
std::vector<int> v{1, 2, 3};
std::ranges::sort(v);
}import std; reemplaza docenas de directivas #include con una sola importacion que compila dramaticamente mas rapido porque el module de la biblioteca estandar esta pre-compilado. Tambien existe import std.compat; que adicionalmente proporciona nombres de la biblioteca C en el espacio de nombres global (como printf, strlen).
El soporte de compiladores aun esta madurando: A partir de 2024, GCC, Clang y MSVC soportan modules pero con niveles variables de completitud y diferente integracion con sistemas de compilacion. MSVC tiene el soporte mas maduro.
Integracion con sistemas de compilacion: CMake 3.28+ soporta modules con la caracteristica CXX_MODULE_SETS, pero el ecosistema (escaneadores de dependencias, gestores de paquetes) aun esta poniendose al dia. El orden de compilacion ahora importa — las interfaces de modules deben compilarse antes que sus consumidores.
Sin exportacion de macros: Los modules deliberadamente no exportan macros #define. El codigo que depende de macros de headers (como WIN32_LEAN_AND_MEAN o NOMINMAX) debe manejar esto de forma diferente.
Estrategia de migracion: Comienza con import std; (C++23) para reemplazar los includes de la biblioteca estandar, luego modulariza gradualmente tu propio codigo comenzando con bibliotecas hoja que tienen pocas dependencias.
Al adoptar modules:
1. Manten las interfaces de modules reducidas — solo exporta lo que los consumidores necesitan. Las entidades no exportadas tienen enlace de module y no pueden causar violaciones de ODR.
2. Usa particiones para modules grandes — divide en sub-modules logicos (por ejemplo, :types, :algorithms, :io).
3. Prefiere export namespace — exporta espacios de nombres completos en lugar de declaraciones individuales para mantener la interfaz organizada.
4. No mezcles #include e import para la misma biblioteca — elige uno y se consistente dentro de una unidad de traduccion.
5. Usa extensiones .cppm o .ixx para archivos de interfaz de module para que los sistemas de compilacion puedan identificarlos automaticamente.
- Los modules reemplazan
#includeconimport— sin inclusion textual, sin fuga de macros, sin errores de orden de inclusion - Usa
exportpara hacer declaraciones visibles a los importadores; las entidades no exportadas son privadas del module - Las particiones de modules dividen modules grandes en sub-componentes mientras presentan una interfaz unificada
import std;(C++23) reemplaza todos los includes de la biblioteca estandar con una sola importacion rapida- Los sistemas de compilacion deben compilar las interfaces de modules antes que los consumidores — el orden de compilacion importa
Quiz — Pon a prueba tus conocimientos
(15 XP)1. Cual es la ventaja principal de los modules sobre #include?
2. Que sucede con una funcion definida en un module pero NO marcada con `export`?
3. Que hace `import std;` en C++23?