not - ¿Cómo hago para que una macro C++ se comporte como una función?
define max en c (9)
Como han mencionado otros, debe evitar las macros siempre que sea posible. Son peligrosos en presencia de efectos secundarios si los macro argumentos se evalúan más de una vez. Si conoce el tipo de argumentos (o puede usar auto
función auto
C ++ 0x), puede usar los temporales para aplicar una única evaluación.
Otro problema: ¡el orden en el que suceden las evaluaciones múltiples puede no ser el esperado!
Considera este código:
#include <iostream>
using namespace std;
int foo( int & i ) { return i *= 10; }
int bar( int & i ) { return i *= 100; }
#define BADMACRO( X, Y ) do { /
cout << "X=" << (X) << ", Y=" << (Y) << ", X+Y=" << ((X)+(Y)) << endl; /
} while (0)
#define MACRO( X, Y ) do { /
int x = X; int y = Y; /
cout << "X=" << x << ", Y=" << y << ", X+Y=" << ( x + y ) << endl; /
} while (0)
int main() {
int a = 1; int b = 1;
BADMACRO( foo(a), bar(b) );
a = 1; b = 1;
MACRO( foo(a), bar(b) );
return 0;
}
Y sale como compilado y se ejecuta en mi máquina:
X=100, Y=10000, X+Y=110 X=10, Y=100, X+Y=110
Digamos que por alguna razón necesitas escribir una macro: MACRO(X,Y)
. (Supongamos que hay una buena razón por la que no puede usar una función en línea.) Desea que esta macro emule una llamada a una función sin valor devuelto.
Ejemplo 1: Esto debería funcionar como se esperaba.
if (x > y)
MACRO(x, y);
do_something();
Ejemplo 2: Esto no debería dar como resultado un error de compilación.
if (x > y)
MACRO(x, y);
else
MACRO(y - x, x - y);
Ejemplo 3: Esto no debería compilarse.
do_something();
MACRO(x, y)
do_something();
La forma ingenua de escribir la macro es así:
#define MACRO(X,Y) /
cout << "1st arg is:" << (X) << endl; /
cout << "2nd arg is:" << (Y) << endl; /
cout << "Sum is:" << ((X)+(Y)) << endl;
Esta es una solución muy mala que falla los tres ejemplos, y no debería necesitar explicar por qué.
Ignore lo que realmente hace la macro, ese no es el punto.
Ahora, la forma en que más a menudo veo macros escritas es encerrarlas con llaves, así:
#define MACRO(X,Y) /
{ /
cout << "1st arg is:" << (X) << endl; /
cout << "2nd arg is:" << (Y) << endl; /
cout << "Sum is:" << ((X)+(Y)) << endl; /
}
Esto resuelve el ejemplo 1, porque la macro está en un bloque de instrucción. Pero el ejemplo 2 está roto porque ponemos un punto y coma después de la llamada a la macro. Esto hace que el compilador piense que el punto y coma es una afirmación en sí misma, lo que significa que la instrucción else no se corresponde con ninguna instrucción if. Y, por último, el ejemplo 3 compila OK, aunque no hay punto y coma, porque un bloque de código no necesita un punto y coma.
¿Hay alguna manera de escribir una macro para que pase los tres ejemplos?
Nota: Estoy enviando mi propia respuesta como parte de la forma aceptada de compartir un consejo , pero si alguien tiene una mejor solución siéntase libre de publicarla aquí, es posible que obtenga más votos que mi método. :)
Crea un bloque usando
#define MACRO(...) do { ... } while(false)
No agregue un; después del tiempo (falso)
Hay una solución bastante inteligente:
#define MACRO(X,Y) /
do { /
cout << "1st arg is:" << (X) << endl; /
cout << "2nd arg is:" << (Y) << endl; /
cout << "Sum is:" << ((X)+(Y)) << endl; /
} while (0)
Ahora tiene una sola instrucción de nivel de bloque, que debe ir seguida de un punto y coma. Esto se comporta como se espera y se desea en los tres ejemplos.
Las macros generalmente deben evitarse; Prefiere las funciones en línea para ellos en todo momento. Cualquier compilador que se precie debería ser capaz de delimitar una pequeña función como si fuera una macro, y una función en línea respetará los espacios de nombres y otros ámbitos, así como la evaluación de todos los argumentos una vez.
Si debe ser una macro, un ciclo while (ya sugerido) funcionará, o puede probar el operador de coma:
#define MACRO(X,Y) /
( /
(cout << "1st arg is:" << (X) << endl), /
(cout << "2nd arg is:" << (Y) << endl), /
(cout << "3rd arg is:" << ((X) + (Y)) << endl), /
(void)0 /
)
El (void)0
hace que el enunciado evalúe uno de tipo void
, y el uso de comas en lugar de punto y coma permite usarlo dentro de un enunciado, en lugar de solo como un elemento independiente. Todavía recomendaría una función en línea por una serie de razones, la menor de las cuales es el alcance y el hecho de que MACRO(a++, b++)
incrementará a
y b
dos veces.
Si estás dispuesto a adoptar la práctica de usar siempre llaves en tus declaraciones if,
Su macro simplemente estaría perdiendo el último punto y coma:
#define MACRO(X,Y) /
cout << "1st arg is:" << (X) << endl; /
cout << "2nd arg is:" << (Y) << endl; /
cout << "Sum is:" << ((X)+(Y)) << endl
Ejemplo 1: (compila)
if (x > y) {
MACRO(x, y);
}
do_something();
Ejemplo 2: (compila)
if (x > y) {
MACRO(x, y);
} else {
MACRO(y - x, x - y);
}
Ejemplo 3: (no compila)
do_something();
MACRO(x, y)
do_something();
Su respuesta sufre del problema de la evaluación múltiple, por lo que (por ejemplo)
macro( read_int(file1), read_int(file2) );
hará algo inesperado y probablemente no deseado.
C ++ 11 nos trajo lambdas, que puede ser increíblemente útil en esta situación:
#define MACRO(X,Y) /
[&](x_, y_) { /
cout << "1st arg is:" << x_ << endl; /
cout << "2nd arg is:" << y_ << endl; /
cout << "Sum is:" << (x_ + y_) << endl; /
}((X), (Y))
Mantiene el poder generativo de las macros, pero tiene un alcance cómodo desde el que puede devolver lo que quiera (incluido el void
). Además, se evita el problema de evaluar los parámetros de macro varias veces.
Sé que dijiste "ignora lo que hace la macro", pero la gente encontrará esta pregunta buscando basándose en el título, por lo que creo que se justifica la discusión de nuevas técnicas para emular funciones con macros.
Lo más cerca que sé de es:
#define MACRO(X,Y) /
do { /
auto MACRO_tmp_1 = (X); /
auto MACRO_tmp_2 = (Y); /
using std::cout; /
using std::endl; /
cout << "1st arg is:" << (MACRO_tmp_1) << endl; /
cout << "2nd arg is:" << (MACRO_tmp_2) << endl; /
cout << "Sum is:" << (MACRO_tmp_1 + MACRO_tmp_2) << endl; /
} while(0)
Esto hace lo siguiente:
- Funciona correctamente en cada uno de los contextos establecidos.
- Evalúa cada uno de sus argumentos exactamente una vez, lo cual es una característica garantizada de una llamada a función (suponiendo que en ambos casos no hay excepciones en ninguna de esas expresiones).
- Actúa en cualquier tipo, mediante el uso de "auto" de C ++ 0x. Esto aún no es C ++ estándar, pero no hay otra forma de obtener las variables tmp necesarias por la regla de evaluación única.
- No requiere que la persona que llama tenga nombres importados del espacio de nombres std, lo que hace la macro original, pero una función no lo haría.
Sin embargo, aún difiere de una función en que:
- En algunos usos no válidos puede dar diferentes errores o advertencias del compilador.
- Se produce un error si X o Y contienen usos de ''MACRO_tmp_1'' o ''MACRO_tmp_2'' del alcance circundante.
- Relacionado con la característica estándar del espacio de nombres: una función usa su propio contexto léxico para buscar nombres, mientras que una macro usa el contexto de su sitio de llamadas. No hay forma de escribir una macro que se comporte como una función al respecto.
- No se puede usar como la expresión de retorno de una función vacía, que puede ser una expresión vacía (como la solución de coma). Esto es aún más un problema cuando el tipo de devolución deseado no es nulo, especialmente cuando se usa como un valor l. Pero la solución de coma no puede incluir el uso de declaraciones, porque son enunciados, así que elija uno o use la extensión ({...}) GNU.
¡Aquí hay una respuesta que viene directamente de libc6
! Echando un vistazo a /usr/include/x86_64-linux-gnu/bits/byteswap.h
, encontré el truco que estabas buscando.
Algunas críticas de soluciones anteriores:
- La solución de Kip no permite evaluar una expresión , que al final suele ser necesaria.
- La solución de coppro no permite asignar una variable ya que las expresiones están separadas, pero pueden evaluar una expresión.
- La solución de Steve Jessop utiliza la palabra clave
auto
C ++ 11, está bien, pero puede usar el tipo conocido / esperado en su lugar.
El truco es usar tanto el constructo (expr,expr)
como el {}
alcance:
#define MACRO(X,Y) /
( /
{ /
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); /
std::cout << "1st arg is:" << __x << std::endl; /
std::cout << "2nd arg is:" << __y << std::endl; /
std::cout << "Sum is:" << (__x + __y) << std::endl; /
__x + __y; /
} /
)
Tenga en cuenta el uso de la palabra clave register
, es solo una pista para el compilador. Los parámetros de macro X
e Y
están (ya) rodeados entre paréntesis y convertidos a un tipo esperado. Esta solución funciona correctamente con el incremento previo y posterior ya que los parámetros se evalúan solo una vez.
Para el propósito de ejemplo, aunque no se solicitó, agregué el __x + __y;
declaración, que es la forma de hacer que todo el bloque sea evaluado como esa expresión precisa.
Es más seguro usar void();
si desea asegurarse de que la macro no se evalúe en una expresión, siendo así ilegal donde se espera un valor de rvalue
.
Sin embargo , la solución no cumple con ISO C ++, ya que se quejará de g++ -pedantic
:
warning: ISO C++ forbids braced-groups within expressions [-pedantic]
Para dar descanso a g++
, use (__extension__ OLD_WHOLE_MACRO_CONTENT_HERE)
para que se lea la nueva definición:
#define MACRO(X,Y) /
(__extension__ ( /
{ /
register int __x = static_cast<int>(X), __y = static_cast<int>(Y); /
std::cout << "1st arg is:" << __x << std::endl; /
std::cout << "2nd arg is:" << __y << std::endl; /
std::cout << "Sum is:" << (__x + __y) << std::endl; /
__x + __y; /
} /
))
Para mejorar mi solución incluso un poco más, usemos la palabra clave __typeof__
, como se ve en MIN y MAX en C :
#define MACRO(X,Y) /
(__extension__ ( /
{ /
__typeof__(X) __x = (X); /
__typeof__(Y) __y = (Y); /
std::cout << "1st arg is:" << __x << std::endl; /
std::cout << "2nd arg is:" << __y << std::endl; /
std::cout << "Sum is:" << (__x + __y) << std::endl; /
__x + __y; /
} /
))
Ahora el compilador determinará el tipo apropiado. Esto también es una extensión de gcc
.
Tenga en cuenta la eliminación de la palabra clave de register
, como lo haría con la siguiente advertencia cuando se utiliza con un tipo de clase:
warning: address requested for ‘__x’, which is declared ‘register’ [-Wextra]