cpp-macros

Qué son las macros en C++

Las macros son una herramienta que nos permite realizar sustituciones de texto antes de la compilación del código.

Las macros son una funcionalidad del preprocesador de C++ (etapa anterior a la compilación del código). El preprocesador lee el código fuente y, cuando encuentra una referencia a una macro, reemplaza el texto con el código o la expresión asociada a la macro.

Las macros son una característica heredada del lenguaje C

En C++ es frecuente abusa vilmente del uso de macros, bien por costumbre o por la idea (equivocada) de que son la alternativa más eficientes. Pero C++, casi siempre, tenemos alternativas mejores.

Evitad usarlas por todos los medios. Es más, que no uséis macros 😅

Definición de Macros

Las macros se definen utilizando la directiva #define seguida del nombre de la macro y su reemplazo.

#define NOMBRE_MACRO sustitucion

Son las macros más simples y consisten en la sustitución de un identificador por un valor constante o una expresión.

#define PI 3.14159
#define MAX_SIZE 100

En este caso, PI y MAX_SIZE son macros de objetos que se reemplazarán por 3.14159 y 100 respectivamente en todo el código donde se utilicen.

#define PI 3.14159

int main() {
    double radio = 5.0;
    double area = PI * radio * radio;
    return 0;
}

En este ejemplo,

  • La macro PI se utiliza para calcular el área de un círculo.
  • Antes de la compilación, el preprocesador reemplazará PI por 3.14159.

También es posible definir macros que aceptan parámetros, lo que las hace similares a las funciones.

Pero con una diferencia muy importante, las macros se expanden en tiempo de compilación (en lugar de evaluarse en tiempo de ejecución)

#define SQUARE(x) ((x) * (x))

int main() {
    int valor = 5;
    int resultado = SQUARE(valor);
    return 0;
}

Aquí, la macro SQUARE(x) se expande a ((valor) * (valor)), resultando en 25.

Desdefinir macros

Podemos eliminar la definición de una macro usando #undef. De hecho, es buena práctica eliminarla para evitar posibles conflictos en otras partes del código.

#define MAX_SIZE 100
// ... uso de MAX_SIZE ...
#undef MAX_SIZE

Ventajas y desventajas

Si antes os he dicho que evitéis usar las macros lo más posible, lógicamente es porque tienen desventajas. Vamos a hablar un poco de esto 👇

Más o menos todas son ciertas, pero también hay una serie de desventajas

Desventajas

  • Errores difíciles de depurar: Debido a que las macros simplemente reemplazan texto, pueden ser un infieeeerno de deputar y de arreglar

  • Se saltan el tipado: Las macros no respetan las reglas de tipado de C++

  • Incompatibilidad con otras partes del código: Las macros dan TODO tipo de problemas al mezclarse con otras partes del código

  • Evaluación múltiple de expresiones: En las macros de función, los argumentos pueden evaluarse más de una vez, lo que puede conducir a comportamientos indeseados.

    #define SQUARE(x) ((x) * (x))
    
    int a = 5;
    int resultado = SQUARE(a++); // a++ se evalúa dos veces

En resumen, que es una práctica muy extendida, que siempre se defiende desde el punto de vista de la eficiencia (principalmente). Pero tenemos métodos prácticamente igual de eficientes, y mucho más limpios.

Prácticas recomendadas al usar macros

Si vais a usar Macros (y en algún momento las usaréis, porque no me hacéis caso 😆) al menos, tened en cuenta siempre estas prácticas

  • Usar paréntesis en TODO: Siempre que definas una macro de función, asegúrate de usar paréntesis alrededor de los argumentos y la expresión completa para evitar problemas con la precedencia de operadores.

    #define SQUARE(x) ((x) * (x))
  • Evitar side effects: Evita pasar expresiones con efectos secundarios (como incrementos ++ o decrementos --) a las macros de función.

  • Prefiere las constantes y funciones en lugar de macros: En muchos casos, el uso de constantes (const) y funciones inline puede ser una alternativa más segura y con mejor tipado que las macros.

    const double PI = 3.14159;
    
    inline int Square(int x) { return x * x; }