Siempre he preguntado esto pero nunca he recibido una respuesta realmente buena; Creo que casi cualquier programador antes incluso de escribir el primer "Hola mundo" se había encontrado con una frase como "la macro nunca debería usarse", "las macro son malas" y así sucesivamente, mi pregunta es: ¿por qué? Con el nuevo C ++ 11, ¿existe una alternativa real después de tantos años?
La parte fácil se trata de macros como #pragma
, que son específicas de la plataforma y específicas del compilador, y la mayoría de las veces tienen fallas graves como #pragma once
esa es propensa a errores en al menos 2 situaciones importantes: el mismo nombre en diferentes rutas y con algunas configuraciones de red y sistemas de archivos.
Pero, en general, ¿qué pasa con las macros y las alternativas a su uso?
fuente
#pragma
no es macro.#pragma
.constexpr
,inline
functions ytemplates
, peroboost.preprocessor
ychaos
demostrar que las macros tienen su lugar. Sin mencionar las macros de configuración para compiladores de diferencias, plataformas, etc.Respuestas:
Las macros son como cualquier otra herramienta: un martillo utilizado en un asesinato no es malo porque es un martillo. Es malvado en la forma en que la persona lo usa de esa manera. Si quieres clavar clavos, un martillo es una herramienta perfecta.
Hay algunos aspectos de las macros que las hacen "malas" (ampliaré cada una de ellas más adelante y sugeriré alternativas):
Así que ampliemos un poco aquí:
1) Las macros no se pueden depurar. Cuando tiene una macro que se traduce en un número o una cadena, el código fuente tendrá el nombre de la macro, y muchos depuradores, no puede "ver" a qué se traduce la macro. Así que no sabes realmente qué está pasando.
Reemplazo : use
enum
oconst T
Para macros "similares a funciones", debido a que el depurador trabaja en un nivel "por línea de origen donde se encuentre", su macro actuará como una sola declaración, sin importar si es una declaración o cien. Hace que sea difícil averiguar qué está pasando.
Reemplazo : use funciones - en línea si necesita ser "rápido" (pero tenga en cuenta que demasiado en línea no es algo bueno)
2) Las macro expansiones pueden tener efectos secundarios extraños.
El famoso es
#define SQUARE(x) ((x) * (x))
y el usox2 = SQUARE(x++)
. Eso lleva ax2 = (x++) * (x++);
que, incluso si fuera un código válido [1], es casi seguro que no sería lo que el programador quería. Si fuera una función, estaría bien hacer x ++, y x solo se incrementaría una vez.Otro ejemplo es "si más" en macros, digamos que tenemos esto:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
y entonces
if (something) safe_divide(b, a, x); else printf("Something is not set...");
De hecho, se convierte en algo completamente incorrecto ...
Reemplazo : funciones reales.
3) Las macros no tienen espacio de nombres
Si tenemos una macro:
#define begin() x = 0
y tenemos un código en C ++ que usa begin:
std::vector<int> v; ... stuff is loaded into v ... for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it) std::cout << ' ' << *it;
Ahora, ¿qué mensaje de error crees que obtienes y dónde buscas un error [suponiendo que hayas olvidado por completo, o ni siquiera conozcas, la macro de inicio que se encuentra en algún archivo de encabezado que alguien más escribió? [y aún más divertido si incluye esa macro antes de la inclusión - se ahogaría en errores extraños que no tienen absolutamente ningún sentido cuando mira el código en sí.
Reemplazo : Bueno, no hay tanto un reemplazo como una "regla"; solo use nombres en mayúsculas para macros y nunca use nombres en mayúsculas para otras cosas.
4) Las macros tienen efectos que no te das cuenta
Toma esta función:
#define begin() x = 0 #define end() x = 17 ... a few thousand lines of stuff here ... void dostuff() { int x = 7; begin(); ... more code using x ... printf("x=%d\n", x); end(); }
Ahora, sin mirar la macro, pensaría que begin es una función, que no debería afectar a x.
Este tipo de cosas, y he visto ejemplos mucho más complejos, ¡REALMENTE pueden arruinar tu día!
Reemplazo : no use una macro para establecer x, o pase x como argumento.
Hay ocasiones en las que el uso de macros es definitivamente beneficioso. Un ejemplo es envolver una función con macros para pasar información de archivo / línea:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__) #define free(x) my_debug_free(x, __FILE__, __LINE__)
Ahora podemos usarlo
my_debug_malloc
como el malloc regular en el código, pero tiene argumentos adicionales, por lo que cuando llega al final y escaneamos "qué elementos de memoria no se han liberado", podemos imprimir dónde se realizó la asignación para que el El programador puede rastrear la fuga.[1] Es un comportamiento indefinido actualizar una variable más de una vez "en un punto de secuencia". Un punto de secuencia no es exactamente lo mismo que una declaración, pero para la mayoría de las intenciones y propósitos, eso es lo que deberíamos considerarlo. Por lo tanto, hacerlo
x++ * x++
se actualizaráx
dos veces, lo que no está definido y probablemente conducirá a valores diferentes en diferentes sistemas y también a valores de resultado diferentesx
.fuente
if else
problemas se pueden resolver envolviendo el cuerpo de la macro en su interiordo { ... } while(0)
. Este se comporta como cabría esperar con respecto aif
yfor
y otros problemas de flujo de control potencialmente riesgosos. Pero sí, una función real suele ser una mejor solución.#define macro(arg1) do { int x = func(arg1); func2(x0); } while(0)
note: expanded from macro 'begin'
y mostrarán dóndebegin
se define.El dicho "las macros son malas" generalmente se refiere al uso de #define, no de #pragma.
En concreto, la expresión se refiere a estos dos casos:
definir números mágicos como macros
usando macros para reemplazar expresiones
Sí, para los elementos de la lista anterior (los números mágicos deben definirse con const / constexpr y las expresiones deben definirse con funciones [normal / inline / template / inline template].
Estos son algunos de los problemas introducidos al definir números mágicos como macros y reemplazar expresiones con macros (en lugar de definir funciones para evaluar esas expresiones):
al definir macros para números mágicos, el compilador no retiene información de tipo para los valores definidos. Esto puede provocar advertencias (y errores) de compilación y confundir a las personas que depuran el código.
al definir macros en lugar de funciones, los programadores que usan ese código esperan que funcionen como funciones y no es así.
Considere este código:
#define max(a, b) ( ((a) > (b)) ? (a) : (b) ) int a = 5; int b = 4; int c = max(++a, b);
Es de esperar que ayc sean 6 después de la asignación ac (como lo haría, con std :: max en lugar de la macro). En cambio, el código realiza:
int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
Además de esto, las macros no admiten espacios de nombres, lo que significa que la definición de macros en su código limitará el código del cliente en los nombres que pueden usar.
Esto significa que si define la macro anterior (para max), ya no podrá hacerlo
#include <algorithm>
en ninguno de los códigos siguientes, a menos que escriba explícitamente:#ifdef max #undef max #endif #include <algorithm>
Tener macros en lugar de variables / funciones también significa que no puede tomar su dirección:
si una macro como constante se evalúa como un número mágico, no puede pasarlo por dirección
para una macro como función, no puede usarla como un predicado o tomar la dirección de la función o tratarla como un funtor.
Editar: como ejemplo, la alternativa correcta a lo
#define max
anterior:template<typename T> inline T max(const T& a, const T& b) { return a > b ? a : b; }
Esto hace todo lo que hace la macro, con una limitación: si los tipos de argumentos son diferentes, la versión de la plantilla te obliga a ser explícito (lo que en realidad conduce a un código más seguro y explícito):
int a = 0; double b = 1.; max(a, b);
Si este máximo se define como una macro, el código se compilará (con una advertencia).
Si este máximo se define como una función de plantilla, el compilador señalará la ambigüedad y usted tendrá que decir
max<int>(a, b)
omax<double>(a, b)
(y así declarar explícitamente su intención).fuente
const int someconstant = 437;
, y puede usarse en casi todas las formas en que se usaría una macro. Lo mismo para funciones pequeñas. Hay algunas cosas en las que puede escribir algo como una macro que no funcionará en una expresión regular en C (podría hacer algo que promedie una matriz de cualquier tipo de número, lo que C no puede hacer, pero C ++ tiene plantillas para eso). Si bien C ++ 11 agrega algunas cosas más que "no necesitas macros para esto", en su mayoría ya está resuelto en C / C ++ anterior.max
ymin
si van seguidos de un paréntesis izquierdo. Pero no debe definir tales macros ...Un problema común es este:
#define DIV(a,b) a / b printf("25 / (3+2) = %d", DIV(25,3+2));
Imprimirá 10, no 5, porque el preprocesador lo expandirá de esta manera:
printf("25 / (3+2) = %d", 25 / 3 + 2);
Esta versión es más segura:
#define DIV(a,b) (a) / (b)
fuente
DIV
macro puede reescribirse con un par de () alrededorb
.#define DIV(a,b)
no#define DIV (a,b)
, que es muy diferente.#define DIV(a,b) (a) / (b)
No es suficientemente bueno; como práctica general, agregue siempre los corchetes más externos, como este:#define DIV(a,b) ( (a) / (b) )
Las macros son valiosas especialmente para crear código genérico (los parámetros de la macro pueden ser cualquier cosa), a veces con parámetros.
Además, este código se coloca (es decir, se inserta) en el punto en el que se utiliza la macro.
OTOH, se pueden lograr resultados similares con:
funciones sobrecargadas (diferentes tipos de parámetros)
plantillas, en C ++ (tipos y valores de parámetros genéricos)
funciones en línea (coloque el código donde se llaman, en lugar de saltar a una definición de un solo punto; sin embargo, esto es más bien una recomendación para el compilador).
editar: en cuanto a por qué las macro son malas:
1) no hay verificación de tipos de los argumentos (no tienen tipo), por lo que se pueden usar incorrectamente fácilmente 2) a veces se expanden a un código muy complejo, que puede ser difícil de identificar y comprender en el archivo preprocesado 3) es fácil cometer errores -código propenso en macros, como:
#define MULTIPLY(a,b) a*b
y luego llamar
MULTIPLY(2+3,4+5)
que se expande en
2 + 3 * 4 + 5 (y no en: (2 + 3) * (4 + 5)).
Para tener este último, debe definir:
#define MULTIPLY(a,b) ((a)*(b))
fuente
No creo que haya nada de malo en usar macros o definiciones de preprocesador como usted las llama.
Son un concepto de (meta) lenguaje que se encuentra en c / c ++ y, como cualquier otra herramienta, pueden hacerte la vida más fácil si sabes lo que estás haciendo. El problema con las macros es que se procesan antes que su código c / c ++ y generan un nuevo código que puede ser defectuoso y causar errores de compilación que son casi obvios. En el lado positivo, pueden ayudarlo a mantener limpio su código y ahorrarle mucho escribir si se usa correctamente, por lo que se reduce a preferencias personales.
fuente
Las macros en C / C ++ pueden servir como una herramienta importante para el control de versiones. Se puede entregar el mismo código a dos clientes con una configuración menor de macros. Yo uso cosas como
#define IBM_AS_CLIENT #ifdef IBM_AS_CLIENT #define SOME_VALUE1 X #define SOME_VALUE2 Y #else #define SOME_VALUE1 P #define SOME_VALUE2 Q #endif
Este tipo de funcionalidad no es posible sin macros. Las macros son en realidad una gran herramienta de administración de configuración de software y no solo una forma de crear accesos directos para reutilizar el código. Definir funciones con el propósito de reutilizarlas en macros definitivamente puede crear problemas.
fuente
Creo que el problema es que las macros no están bien optimizadas por el compilador y son "feas" de leer y depurar.
A menudo, una buena alternativa son funciones genéricas y / o funciones en línea.
fuente
Las macros de preprocesador no son malas cuando se utilizan para fines previstos como:
Alternativas: uno puede usar algún tipo de archivos de configuración en formato ini, xml, json para propósitos similares. Pero su uso tendrá efectos en el tiempo de ejecución en el código que una macro de preprocesador puede evitar.
fuente