Aparte del hecho de que su macro es una inty su constexpr unsignedes una unsigned, existen diferencias importantes y las macros solo tienen una ventaja.
Alcance
Una macro la define el preprocesador y simplemente se sustituye en el código cada vez que ocurre. El preprocesador es tonto y no comprende la sintaxis ni la semántica de C ++. Las macros ignoran ámbitos como espacios de nombres, clases o bloques de funciones, por lo que no puede usar un nombre para nada más en un archivo fuente. Eso no es cierto para una constante definida como una variable C ++ adecuada:
Está bien tener una variable miembro llamada max_heightporque es un miembro de la clase y, por lo tanto, tiene un alcance diferente y es diferente de la que se encuentra en el alcance del espacio de nombres. Si intenta reutilizar el nombre MAX_HEIGHTdel miembro, el preprocesador lo cambiaría a esta tontería que no compilaría:
classWindow {// ...int720;
};
Es por eso que debe proporcionar macros UGLY_SHOUTY_NAMESpara asegurarse de que se destaquen y puede tener cuidado al nombrarlas para evitar conflictos. Si no usa macros innecesariamente, no tiene que preocuparse por eso (y no tiene que leer SHOUTY_NAMES).
Si solo desea una constante dentro de una función, no puede hacer eso con una macro, porque el preprocesador no sabe qué es una función o qué significa estar dentro de ella. Para limitar una macro a solo una determinada parte de un archivo, debe volver a #undefhacerlo:
Una variable constexpr es una variable, por lo que realmente existe en el programa y puede hacer cosas normales de C ++ como tomar su dirección y vincular una referencia a ella.
El problema es que MAX_HEIGHTno es una variable, por lo que la llamada a std::maxun temporal intdebe ser creada por el compilador. La referencia que devuelve std::maxpodría referirse a ese temporal, que no existe después del final de esa declaración, por lo que return haccede a la memoria no válida.
Ese problema simplemente no existe con una variable adecuada, porque tiene una ubicación fija en la memoria que no desaparece:
No puede usar una variable aquí, porque el preprocesador no entiende cómo referirse a las variables por su nombre. Solo comprende cosas muy básicas como la expansión macro y las directivas que comienzan con #(como #includey #definey #if).
Si desea una constante que el preprocesador pueda entender , debe usar el preprocesador para definirla. Si desea una constante para el código C ++ normal, utilice el código C ++ normal.
El ejemplo anterior es solo para demostrar una condición de preprocesador, pero incluso ese código podría evitar el uso del preprocesador:
using height_type = std::conditional_t<max_height < 256, unsignedchar, unsignedint>;
Una constexprvariable no necesita ocupar memoria hasta que se toma su dirección (un puntero / referencia); de lo contrario, se puede optimizar completamente (y creo que podría haber Standardese que lo garantice). Quiero enfatizar esto para que la gente no continúe usando el viejo e inferior ' enumtruco' debido a una idea equivocada de que un trivial constexprque no requiere almacenamiento ocupará algunos.
underscore_d
3
Su sección "Una ubicación de memoria real" es incorrecta: 1. Está regresando por valor (int), por lo que se realiza una copia, el temporal no es un problema. 2. Si hubiera regresado por referencia (int &), entonces su int heightsería tan problemático como la macro, ya que su alcance está vinculado a la función, esencialmente temporal también. 3. El comentario anterior, "const int & h extenderá la vida útil del temporal" es correcto.
PoweredByRice
4
@underscore_d verdadero, pero eso no cambia el argumento. La variable no requerirá almacenamiento a menos que haya un uso odr de la misma. El punto es que cuando se requiere una variable real con almacenamiento, la variable constexpr hace lo correcto.
Jonathan Wakely
1
@PoweredByRice 1. el problema no tiene nada que ver con el valor de retorno de limit, el problema es el valor de retorno de std::max. 2. sí, por eso no devuelve una referencia. 3. incorrecto, vea el enlace coliru arriba.
Jonathan Wakely
3
@PoweredByRice suspiro, realmente no necesitas explicarme cómo funciona C ++. Si tiene const int& h = max(x, y);y maxdevuelve por el valor, se extiende la vida útil de su valor de devolución. No por el tipo de retorno, sino por el const int&que está vinculado. Lo que escribí es correcto.
Jonathan Wakely
11
En términos generales, debe usar constexprsiempre que pueda, y macros solo si no es posible otra solución.
Razón fundamental:
Las macros son un simple reemplazo en el código y, por esta razón, a menudo generan conflictos (por ejemplo, maxmacros windows.h vs std::max). Además, una macro que funciona puede usarse fácilmente de una manera diferente, lo que puede provocar extraños errores de compilación. (p.ejQ_PROPERTY usado en miembros de estructura)
Debido a todas esas incertidumbres, es un buen estilo de código evitar las macros, exactamente como normalmente evitarías los gotos.
constexpr se define semánticamente y, por lo tanto, generalmente genera muchos menos problemas.
Compilación condicional usando, por #ifejemplo, cosas para las que el preprocesador es realmente útil. Definir una constante no es una de las cosas para las que el preprocesador es útil, a menos que esa constante deba ser una macro porque se usa en condiciones de preprocesador usando #if. Si la constante es para uso en código C ++ normal (no directivas de preprocesador), entonces use una variable C ++ normal, no una macro de preprocesador.
Jonathan Wakely
Excepto el uso de macros variadic, principalmente el uso de macros para conmutadores del compilador, pero intentar reemplazar las declaraciones de macro actuales (como condicional, conmutadores de cadena literal) que tratan con declaraciones de código real con constexpr es una buena idea.
Yo diría que los cambios de compilador tampoco son una buena idea. Sin embargo, entiendo completamente que es necesario algunas veces (también macros), especialmente cuando se trata de código multiplataforma o incrustado. Para responder a su pregunta: si ya está tratando con un preprocesador, usaría macros para mantener claro e intuitivo qué es el preprocesador y el tiempo de compilación. También sugeriría comentar mucho y hacer que su uso sea lo más breve y local posible (evite que las macros se extiendan o 100 líneas # si). Quizás la excepción sea la típica protección #ifndef (estándar para #pragma once) que se comprende bien.
Adrian Maire
3
Gran respuesta de Jonathon Wakely . También te aconsejo que eches un vistazo a la respuesta de jogojapan en cuanto a cuál es la diferencia entre constyconstexpr antes de que consideres el uso de macros.
Las macros son tontas, pero en el buen sentido. Aparentemente, hoy en día son una ayuda de compilación para cuando desea que partes muy específicas de su código solo se compilen en presencia de ciertos parámetros de compilación que se "definen". Por lo general, todo lo que eso significa es tomar el nombre de su macro, o mejor aún, llamémoslo ay Triggeragregar cosas como /D:Trigger,-DTrigger , etc, para las herramientas de construcción que se utiliza.
Si bien hay muchos usos diferentes para las macros, estos son los dos que veo con más frecuencia que no son prácticas malas / desactualizadas:
Secciones de código específicas de plataforma y hardware
Mayor verbosidad se construye
Entonces, si bien puede, en el caso del OP, lograr el mismo objetivo de definir un int con constexpro a MACRO, es poco probable que los dos se superpongan cuando se usan las convenciones modernas. Aquí hay algunos usos comunes de macros que aún no se han eliminado.
#if defined VERBOSE || defined DEBUG || defined MSG_ALL// Verbose message-handling code here#endif
Como otro ejemplo de uso de macros, digamos que tiene algún hardware próximo para lanzar, o tal vez una generación específica del mismo que tiene algunas soluciones difíciles que los demás no requieren. Definiremos esta macro como GEN_3_HW.
#if defined GEN_3_HW && defined _WIN64// Windows-only special handling for 64-bit upcoming hardware#elif defined GEN_3_HW && defined __APPLE__// Special handling for macs on the new hardware#elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__// Greetings, Outlander! ;)#else// Generic handling#endif
Respuestas:
No absolutamente no. Ni siquiera cerca.
Aparte del hecho de que su macro es una
int
y suconstexpr unsigned
es unaunsigned
, existen diferencias importantes y las macros solo tienen una ventaja.Alcance
Una macro la define el preprocesador y simplemente se sustituye en el código cada vez que ocurre. El preprocesador es tonto y no comprende la sintaxis ni la semántica de C ++. Las macros ignoran ámbitos como espacios de nombres, clases o bloques de funciones, por lo que no puede usar un nombre para nada más en un archivo fuente. Eso no es cierto para una constante definida como una variable C ++ adecuada:
#define MAX_HEIGHT 720 constexpr int max_height = 720; class Window { // ... int max_height; };
Está bien tener una variable miembro llamada
max_height
porque es un miembro de la clase y, por lo tanto, tiene un alcance diferente y es diferente de la que se encuentra en el alcance del espacio de nombres. Si intenta reutilizar el nombreMAX_HEIGHT
del miembro, el preprocesador lo cambiaría a esta tontería que no compilaría:class Window { // ... int 720; };
Es por eso que debe proporcionar macros
UGLY_SHOUTY_NAMES
para asegurarse de que se destaquen y puede tener cuidado al nombrarlas para evitar conflictos. Si no usa macros innecesariamente, no tiene que preocuparse por eso (y no tiene que leerSHOUTY_NAMES
).Si solo desea una constante dentro de una función, no puede hacer eso con una macro, porque el preprocesador no sabe qué es una función o qué significa estar dentro de ella. Para limitar una macro a solo una determinada parte de un archivo, debe volver a
#undef
hacerlo:int limit(int height) { #define MAX_HEIGHT 720 return std::max(height, MAX_HEIGHT); #undef MAX_HEIGHT }
Compare con el mucho más sensato:
int limit(int height) { constexpr int max_height = 720; return std::max(height, max_height); }
¿Por qué preferirías el macro?
Una ubicación de memoria real
Una variable constexpr es una variable, por lo que realmente existe en el programa y puede hacer cosas normales de C ++ como tomar su dirección y vincular una referencia a ella.
Este código tiene un comportamiento indefinido:
#define MAX_HEIGHT 720 int limit(int height) { const int& h = std::max(height, MAX_HEIGHT); // ... return h; }
El problema es que
MAX_HEIGHT
no es una variable, por lo que la llamada astd::max
un temporalint
debe ser creada por el compilador. La referencia que devuelvestd::max
podría referirse a ese temporal, que no existe después del final de esa declaración, por lo quereturn h
accede a la memoria no válida.Ese problema simplemente no existe con una variable adecuada, porque tiene una ubicación fija en la memoria que no desaparece:
int limit(int height) { constexpr int max_height = 720; const int& h = std::max(height, max_height); // ... return h; }
(En la práctica, probablemente declararía que
int h
no,const int& h
pero el problema puede surgir en contextos más sutiles).Condiciones del preprocesador
El único momento para preferir una macro es cuando necesita que el preprocesador entienda su valor, para su uso en
#if
condiciones, p. Ej.#define MAX_HEIGHT 720 #if MAX_HEIGHT < 256 using height_type = unsigned char; #else using height_type = unsigned int; #endif
No puede usar una variable aquí, porque el preprocesador no entiende cómo referirse a las variables por su nombre. Solo comprende cosas muy básicas como la expansión macro y las directivas que comienzan con
#
(como#include
y#define
y#if
).Si desea una constante que el preprocesador pueda entender , debe usar el preprocesador para definirla. Si desea una constante para el código C ++ normal, utilice el código C ++ normal.
El ejemplo anterior es solo para demostrar una condición de preprocesador, pero incluso ese código podría evitar el uso del preprocesador:
using height_type = std::conditional_t<max_height < 256, unsigned char, unsigned int>;
fuente
constexpr
variable no necesita ocupar memoria hasta que se toma su dirección (un puntero / referencia); de lo contrario, se puede optimizar completamente (y creo que podría haber Standardese que lo garantice). Quiero enfatizar esto para que la gente no continúe usando el viejo e inferior 'enum
truco' debido a una idea equivocada de que un trivialconstexpr
que no requiere almacenamiento ocupará algunos.int height
sería tan problemático como la macro, ya que su alcance está vinculado a la función, esencialmente temporal también. 3. El comentario anterior, "const int & h extenderá la vida útil del temporal" es correcto.limit
, el problema es el valor de retorno destd::max
. 2. sí, por eso no devuelve una referencia. 3. incorrecto, vea el enlace coliru arriba.const int& h = max(x, y);
ymax
devuelve por el valor, se extiende la vida útil de su valor de devolución. No por el tipo de retorno, sino por elconst int&
que está vinculado. Lo que escribí es correcto.En términos generales, debe usar
constexpr
siempre que pueda, y macros solo si no es posible otra solución.Razón fundamental:
Las macros son un simple reemplazo en el código y, por esta razón, a menudo generan conflictos (por ejemplo,
max
macros windows.h vsstd::max
). Además, una macro que funciona puede usarse fácilmente de una manera diferente, lo que puede provocar extraños errores de compilación. (p.ejQ_PROPERTY
usado en miembros de estructura)Debido a todas esas incertidumbres, es un buen estilo de código evitar las macros, exactamente como normalmente evitarías los gotos.
constexpr
se define semánticamente y, por lo tanto, generalmente genera muchos menos problemas.fuente
#if
ejemplo, cosas para las que el preprocesador es realmente útil. Definir una constante no es una de las cosas para las que el preprocesador es útil, a menos que esa constante deba ser una macro porque se usa en condiciones de preprocesador usando#if
. Si la constante es para uso en código C ++ normal (no directivas de preprocesador), entonces use una variable C ++ normal, no una macro de preprocesador.Gran respuesta de Jonathon Wakely . También te aconsejo que eches un vistazo a la respuesta de jogojapan en cuanto a cuál es la diferencia entre
const
yconstexpr
antes de que consideres el uso de macros.Las macros son tontas, pero en el buen sentido. Aparentemente, hoy en día son una ayuda de compilación para cuando desea que partes muy específicas de su código solo se compilen en presencia de ciertos parámetros de compilación que se "definen". Por lo general, todo lo que eso significa es tomar el nombre de su macro, o mejor aún, llamémoslo ay
Trigger
agregar cosas como/D:Trigger
,-DTrigger
, etc, para las herramientas de construcción que se utiliza.Si bien hay muchos usos diferentes para las macros, estos son los dos que veo con más frecuencia que no son prácticas malas / desactualizadas:
Entonces, si bien puede, en el caso del OP, lograr el mismo objetivo de definir un int con
constexpr
o aMACRO
, es poco probable que los dos se superpongan cuando se usan las convenciones modernas. Aquí hay algunos usos comunes de macros que aún no se han eliminado.#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif
Como otro ejemplo de uso de macros, digamos que tiene algún hardware próximo para lanzar, o tal vez una generación específica del mismo que tiene algunas soluciones difíciles que los demás no requieren. Definiremos esta macro como
GEN_3_HW
.#if defined GEN_3_HW && defined _WIN64 // Windows-only special handling for 64-bit upcoming hardware #elif defined GEN_3_HW && defined __APPLE__ // Special handling for macs on the new hardware #elif !defined _WIN32 && !defined __linux__ && !defined __APPLE__ && !defined __ANDROID__ && !defined __unix__ && !defined __arm__ // Greetings, Outlander! ;) #else // Generic handling #endif
fuente