c++ c++11 macros constexpr

c++ - Constexpr vs macros



c++11 (3)

¿No son básicamente lo mismo?

No absolutamente no. Ni siquiera cerca.

Además del hecho de que su macro es un int y su constexpr unsigned es un unsigned , existen diferencias importantes y las macros solo tienen una ventaja.

Alcance

El preprocesador define una macro y simplemente se sustituye en el código cada vez que ocurre. El preprocesador es tonto y no entiende la sintaxis o 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 otra cosa 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 clase y, por lo tanto, tiene un alcance diferente y es diferente de la del alcance del espacio de nombres. Si trató de reutilizar el nombre MAX_HEIGHT para el miembro, entonces el preprocesador lo cambiaría a este sinsentido que no compilaría:

class Window { // ... int 720; };

Esta es la razón por la que debe UGLY_SHOUTY_NAMES macros UGLY_SHOUTY_NAMES para asegurarse de que se destacan y puede tener cuidado al nombrarlos 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 hacerlo 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 cierta parte de un archivo, debe #undef nuevamente:

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 en realidad 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 para la llamada a std::max el compilador debe crear un int temporal. La referencia que devuelve std::max podría referirse a esa temporal, que no existe después del final de esa declaración, por lo que return h accede a 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 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 comprenda su valor, para usarlo en condiciones #if , p. Ej.

#define MAX_HEIGHT 720 #if MAX_HEIGHT < 256 using height_type = unsigned char; #else using height_type = unsigned int; #endif

No podría usar una variable aquí, porque el preprocesador no entiende cómo referirse a las variables por su nombre. Solo comprende cosas básicas muy básicas como la expansión de macros y las directivas que comienzan con # (como #include y #define y #if ).

Si desea una constante que pueda ser entendida por el preprocesador, entonces debe usar el preprocesador para definirla. Si desea una constante para el código C ++ normal, use 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>;

¿Dónde debería preferir usar macros y dónde debería preferir constexpr ? ¿No son básicamente lo mismo?

#define MAX_HEIGHT 720

vs

constexpr unsigned int max_height = 720;


En términos generales, debe usar constexpr siempre que pueda, y macros solo si no hay otra solución posible.

Racional:

Las macros son un reemplazo simple en el código, y por esta razón, generalmente generan conflictos (por ejemplo, windows.h max macro vs std :: max). Además, una macro que funciona puede usarse fácilmente de una manera diferente que luego desencadena errores de compilación extraños. (por ejemplo, Q_PROPERTY utilizado en miembros de la estructura)

Debido a todas esas incertidumbres, es un buen estilo de código para evitar macros, exactamente como generalmente se evitan los gotos.

constexpr se define semánticamente y, por lo tanto, genera muchos menos problemas.


Gran respuesta de Jonathon Wakely . También le aconsejo que eche un vistazo a la respuesta de jogojapan sobre cuál es la diferencia entre const y constexpr antes de siquiera considerar el uso de macros.

Las macros son tontas, pero en el buen sentido. Aparentemente hoy en día son una ayuda para la 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 significa es tomar su nombre de macro, o mejor aún, llamémoslo Trigger , y -DTrigger cosas como, /D:Trigger , -DTrigger , etc. a las herramientas de compilación que se utilizan.

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:

  1. Secciones de código de hardware y plataforma específica
  2. Aumento de las construcciones de verbosidad

Entonces, si bien en el caso del OP puede lograr el mismo objetivo de definir un int con constexpr o un MACRO , es poco probable que los dos se superpongan al usar convenciones modernas. Aquí hay algunos macro-usos comunes que aún no se han eliminado.

#if defined VERBOSE || defined DEBUG || defined MSG_ALL // Verbose message-handling code here #endif

Como otro ejemplo para el uso de macros, supongamos que tiene un hardware próximo para lanzar, o tal vez una generación específica que tenga algunas soluciones difíciles que los otros 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