
usuario1849534
Siempre he preguntado esto pero nunca he recibido una respuesta realmente buena; Creo que casi cualquier programador antes incluso de escribir el primer “Hello World” se había encontrado con una frase como “nunca se deben usar las macros”, “las macros 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 es sobre macros como #pragma
que son específicos de la plataforma y del compilador, y la mayoría de las veces tienen fallas graves como #pragma once
eso es propenso 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?

esteras petersson
Las macros son como cualquier otra herramienta: un martillo utilizado en un asesinato no es malo porque es un martillo. Es malo 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” (explicaré cada una más adelante y sugeriré alternativas):
- No puede depurar macros.
- La expansión macro puede provocar efectos secundarios extraños.
- Las macros no tienen “espacio de nombres”, por lo que si tiene una macro que choca con un nombre usado en otro lugar, obtiene reemplazos de macros donde no los quería, y esto generalmente genera mensajes de error extraños.
- Las macros pueden afectar cosas de las que no te das cuenta.
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 pueden “ver” a qué se traduce la macro. Así que en realidad no sabes lo que está pasando.
Reemplazo: Utilizar enum
o const T
Para las macros “similares a funciones”, debido a que el depurador funciona en un nivel “por línea de origen donde se encuentre”, su macro actuará como una declaración única, sin importar si es una declaración o cien. Hace que sea difícil darse cuenta de lo que 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 expansiones macro pueden tener efectos secundarios extraños.
El famoso es #define SQUARE(x) ((x) * (x))
y el uso x2 = SQUARE(x++)
. Eso lleva a x2 = (x++) * (x++);
que, incluso si fuera un código válido [1], es casi seguro que no sería lo que quería el programador. Si fuera una función, estaría bien hacer x++, y x solo se incrementaría una vez.
Otro ejemplo es “if else” en macros, digamos que tenemos esto:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
y luego
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
En realidad, 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 algo de 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 recibes y dónde buscas un error? [assuming you have completely forgotten – or didn’t even know about – the begin macro that lives in some header file that someone else wrote? [and even more fun if you included that macro before the include – you’d be drowning in strange errors that makes absolutely no sense when you look at the code itself.
Replacement: Well there isn’t so much as a replacement as a “rule” – only use uppercase names for macros, and never use all uppercase names for other things.
4) Macros have effects you don’t realize
Take this function:
#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();
}
Now, without looking at the macro, you would think that begin is a function, which shouldn’t affect x.
This sort of thing, and I’ve seen much more complex examples, can REALLY mess up your day!
Replacement: Either don’t use a macro to set x, or pass x in as an argument.
There are times when using macros is definitely beneficial. One example is to wrap a function with macros to pass on file/line information:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Now we can use my_debug_malloc
as the regular malloc in the code, but it has extra arguments, so when it comes to the end and we scan the “which memory elements hasn’t been freed”, we can print where the allocation was made so the programmer can track down the leak.
[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 los intentos y propósitos, así es como deberíamos considerarlo. Así que haciendo x++ * x++
actualizará x
dos veces, lo que no está definido y probablemente conducirá a diferentes valores en diferentes sistemas, y diferente valor de resultado en x
así como.

utnapistim
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:
con el nuevo C++ 11 hay una alternativa real después de tantos años?
Sí, para los elementos de la lista anterior (los números mágicos deben definirse con const/constexpr y las expresiones deben definirse con [normal/inline/template/inline template] funciones
Estos son algunos de los problemas que se presentan 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 causar 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);
Esperaría que a y c fueran 6 después de la asignación a c (como lo haría, usando std::max en lugar de la macro). En su lugar, 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 definir macros en su código limitará el código del cliente en cuanto a los nombres que pueden usar.
Esto significa que si define la macro anterior (para max), ya no podrá #include <algorithm>
en cualquiera de los siguientes códigos, 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 pasarla por dirección
-
para una macro como función, no puede usarla como predicado ni tomar la dirección de la función ni tratarla como un funtor.
Editar: como ejemplo, la alternativa correcta a la #define max
encima:
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 lo 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 tendrá que decir: max<int>(a, b)
o max<double>(a, b)
(y así declarar explícitamente su intención).

faazon
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)
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 que se usa 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é la macro es mala:
1) no hay verificación de tipo de los argumentos (no tienen tipo), por lo que se pueden usar mal fácilmente 2) a veces se expanden en 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 esto último, debe definir:
#define MULTIPLY(a,b) ((a)*(b))
No creo que haya nada de malo en usar definiciones de preprocesador o macros como las llamas.
Son un concepto de (meta) lenguaje que se encuentra en c/c++ y, como cualquier otra herramienta, pueden facilitarle la vida si sabe lo que está haciendo. El problema con las macros es que se procesan antes que su código c/c++ y generan código nuevo que puede ser defectuoso y causar errores de compilación que son casi obvios. En el lado positivo, pueden ayudarlo a mantener su código limpio y ahorrarle mucho tipeo si se usan correctamente, por lo que se trata de una preferencia personal.

indiogarg
Las macros en C/C++ pueden servir como una herramienta importante para el control de versiones. El mismo código se puede entregar 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 tan fácilmente sin macros. Las macros son en realidad una excelente herramienta de administración de configuración de software y no solo una forma de crear accesos directos para reutilizar el código. La definición de funciones con el fin de reutilizarlas en macros definitivamente puede crear problemas.

indiogarg
Las macros de preprocesador no son malas cuando se usan para fines previstos como:
- Crear diferentes versiones del mismo software usando el tipo de construcciones #ifdef, por ejemplo, la versión de ventanas para diferentes regiones.
- Para definir valores relacionados con las pruebas de código.
Alternativas-
Uno puede usar algún tipo de archivos de configuración en formato ini, xml, json para propósitos similares. Pero usarlos tendrá efectos de tiempo de ejecución en el código que una macro de preprocesador puede evitar.
#pragma
no es macro– FooF
26 de diciembre de 2012 a las 13:47
¿Directiva de preprocesador @foof?
– usuario1849534
26 de diciembre de 2012 a las 13:51
@user1849534: Sí, eso es lo que es… y los consejos sobre macros no se refieren a
#pragma
.– Ben Voigt
26 de diciembre de 2012 a las 13:52
Puedes hacer mucho con
constexpr
,inline
funciones, ytemplates
peroboost.preprocessor
ychaos
mostrar que las macros tienen su lugar. Sin mencionar las macros de configuración para diferentes compiladores, plataformas, etc.– Brandón
26 de diciembre de 2012 a las 13:52
Consulte también “¿Son malas todas las macros?”
– Brad Larson
♦
23 de enero de 2013 a las 18:56