'constancia estática' frente a '#definir'
¿Es mejor utilizar static const
variables que #define
preprocesador? ¿O tal vez depende del contexto?
¿Cuáles son las ventajas/desventajas de cada método?
Pros y contras entre #define
s, const
s y (lo que has olvidado) enum
s, según el uso:
enum
s:- sólo es posible para valores enteros
- Los problemas de conflicto entre identificadores y alcance adecuado se manejan muy bien, particularmente en clases de enumeración de C++ 11
enum class X
donde las enumeraciones no están ambiguas según el alcance.X::
- fuertemente tipado, pero a un tamaño int con o sin signo lo suficientemente grande sobre el cual no tienes control en C++03 (aunque puedes especificar un campo de bits en el que deben empaquetarse si la enumeración es miembro de struct/ class/union), mientras que C++ 11 tiene por defecto
int
pero el programador puede configurarlo explícitamente - no puedo tomar la dirección; no hay ninguna, ya que los valores de enumeración se sustituyen efectivamente en línea en los puntos de uso.
- restricciones de uso más estrictas (por ejemplo, incremento:
template <typename T> void f(T t) { cout << ++t; }
no se compilará, aunque puede incluir una enumeración en una clase con un constructor implícito, un operador de conversión y operadores definidos por el usuario) - El tipo de cada constante se toma de la enumeración adjunta, por lo que
template <typename T> void f(T)
se obtiene una instanciación distinta cuando se pasa el mismo valor numérico de diferentes enumeraciones, todas las cuales son distintas de cualquierf(int)
instanciación real. El código objeto de cada función podría ser idéntico (ignorando las compensaciones de direcciones), pero no esperaría que un compilador/enlazador elimine las copias innecesarias, aunque puede verificar su compilador/enlazador si lo desea. - incluso con typeof/decltype, no puedo esperar que numeric_limits proporcione información útil sobre el conjunto de valores y combinaciones significativos (de hecho, las combinaciones "legales" ni siquiera están anotadas en el código fuente, considere
enum { A = 1, B = 2 }
: esA|B
"legal" desde la lógica del programa ¿perspectiva?) - El nombre de tipo de la enumeración puede aparecer en varios lugares en RTTI , mensajes del compilador, etc. - posiblemente útil, posiblemente ofuscado
- no se puede usar una enumeración sin que la unidad de traducción realmente vea el valor, lo que significa que las enumeraciones en las API de la biblioteca necesitan los valores expuestos en el encabezado, y
make
otras herramientas de recompilación basadas en marcas de tiempo activarán la recompilación del cliente cuando se cambien (¡mal! )
const
s:- Los problemas de conflicto de identificadores/alcance adecuado se manejan muy bien
- tipo fuerte, único y especificado por el usuario
- puede intentar "escribir"
#define
ala#define S std::string("abc")
, pero la constante evita la construcción repetida de temporales distintos en cada punto de uso
- puede intentar "escribir"
- Complicaciones de la regla de una definición
- puede tomar direcciones, crear referencias constantes a ellas, etc.
- más similar a un sin
const
valor, que minimiza el trabajo y el impacto si se cambia entre los dos - El valor se puede colocar dentro del archivo de implementación, lo que permite una recompilación localizada y solo enlaces de cliente para recoger el cambio.
#define
s:- alcance "global"/más propenso a usos conflictivos, lo que puede producir problemas de compilación difíciles de resolver y resultados inesperados en tiempo de ejecución en lugar de mensajes de error sensatos; mitigar esto requiere:
- identificadores largos, oscuros y/o coordinados centralmente, y el acceso a ellos no puede beneficiarse de la coincidencia implícita de espacios de nombres usados/actuales/buscados por Koenig, alias de espacios de nombres, etc.
- Si bien la mejor práctica permite que los identificadores de parámetros de plantilla sean letras mayúsculas de un solo carácter (posiblemente seguidas de un número), otros usos de identificadores sin letras minúsculas se reservan convencionalmente y se esperan de las definiciones del preprocesador (fuera del sistema operativo y la biblioteca C/C++). encabezados). Esto es importante para que el uso del preprocesador a escala empresarial siga siendo manejable. Se puede esperar que las bibliotecas de terceros cumplan. Observar esto implica que la migración de constantes o enumeraciones existentes hacia/desde definiciones implica un cambio en las mayúsculas y, por lo tanto, requiere ediciones en el código fuente del cliente en lugar de una recompilación "simple". (Personalmente, escribo en mayúscula la primera letra de las enumeraciones, pero no las constantes, por lo que también me vería obligado a migrar entre esas dos; tal vez sea hora de repensarlo).
- Es posible realizar más operaciones en tiempo de compilación: concatenación literal de cadenas, encadenamiento (tomando su tamaño), concatenación en identificadores.
- La desventaja es que, dado
#define X "x"
y cierto uso del cliente"pre" X "post"
, si desea o necesita hacer de X una variable modificable en tiempo de ejecución en lugar de una constante, fuerza las ediciones en el código del cliente (en lugar de solo la recompilación), mientras que esa transición es más fácil desde aconst char*
oconst std::string
dado que ya obliga al usuario a incorporar operaciones de concatenación (por ejemplo,"pre" + X + "post"
parastring
)
- La desventaja es que, dado
- no se puede usar
sizeof
directamente en un literal numérico definido - sin tipo (GCC no advierte si se compara con
unsigned
) - Es posible que algunas cadenas de compilador/enlazador/depurador no presenten el identificador, por lo que se verá reducido a mirar "números mágicos" (cadenas, lo que sea...)
- no puedo tomar la dirección
- el valor sustituido no necesita ser legal (o discreto) en el contexto donde se crea #define, ya que se evalúa en cada punto de uso, por lo que puede hacer referencia a objetos aún no declarados, depende de la "implementación" que no necesita estar preincluido, crear "constantes" que
{ 1, 2 }
puedan usarse para inicializar matrices, etc.#define MICROSECONDS *1E-6
(¡ definitivamente no recomiendo esto!) - algunas cosas especiales como
__FILE__
y__LINE__
se pueden incorporar a la macro sustitución - puede probar la existencia y el valor en
#if
declaraciones para incluir código condicionalmente (más poderoso que un "if" posterior al preprocesamiento, ya que el código no necesita ser compilable si no lo selecciona el preprocesador), usar#undef
-ine, redefinir, etc. - El texto sustituido debe estar expuesto:
- en la unidad de traducción que la utiliza, lo que significa que las macros en las bibliotecas para uso del cliente deben estar en el encabezado, por lo que
make
otras herramientas de recompilación basadas en marcas de tiempo activarán la recompilación del cliente cuando se cambien (¡mal!). - o en la línea de comando, donde se necesita aún más cuidado para garantizar que el código del cliente se vuelva a compilar (por ejemplo, el Makefile o script que proporciona la definición debe aparecer como una dependencia)
- en la unidad de traducción que la utiliza, lo que significa que las macros en las bibliotecas para uso del cliente deben estar en el encabezado, por lo que
- alcance "global"/más propenso a usos conflictivos, lo que puede producir problemas de compilación difíciles de resolver y resultados inesperados en tiempo de ejecución en lugar de mensajes de error sensatos; mitigar esto requiere:
Mi opinión personal:
Como regla general, uso const
s y los considero la opción más profesional para uso general (aunque los demás tienen una simplicidad que atrae a este viejo programador vago).
Personalmente, detesto el preprocesador, así que siempre elegiría const
.
La principal ventaja de a #define
es que no requiere memoria para almacenarse en su programa, ya que en realidad solo reemplaza algo de texto con un valor literal. También tiene la ventaja de que no tiene tipo, por lo que puede usarse para cualquier valor entero sin generar advertencias.
Las ventajas de " const
"s son que se les puede establecer un alcance y se pueden usar en situaciones en las que es necesario pasar un puntero a un objeto.
Sin embargo , no sé exactamente a qué te refieres con la static
parte " ". Si declaras globalmente, lo pondría en un espacio de nombres anónimo en lugar de usar static
. Por ejemplo
namespace {
unsigned const seconds_per_minute = 60;
};
int main (int argc; char *argv[]) {
...
}
Si se trata de una pregunta de C++ y se menciona #define
como alternativa, entonces se trata de constantes "globales" (es decir, de alcance de archivo), no de miembros de la clase. Cuando se trata de este tipo de constantes en C++ static const
es redundante. En C++ const
tienen enlaces internos de forma predeterminada y no tiene sentido declararlos static
. Así que en realidad se trata const
de vs.#define
Y, por último, const
es preferible en C++. Al menos porque dichas constantes están tipificadas y tienen alcance. Simplemente no hay razones para preferirlo #define
, const
salvo algunas excepciones.
Las constantes de cadena, por cierto, son un ejemplo de tal excepción. Con #define
las constantes de cadena d se puede utilizar la función de concatenación en tiempo de compilación de los compiladores C/C++, como en
#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"
const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;
PD Nuevamente, por si acaso, cuando alguien menciona static const
como alternativa a #define
, generalmente significa que está hablando de C, no de C++. Me pregunto si esta pregunta está etiquetada correctamente...
#define
puede conducir a resultados inesperados:
#include <iostream>
#define x 500
#define y x + 5
int z = y * 2;
int main()
{
std::cout << "y is " << y;
std::cout << "\nz is " << z;
}
Genera un resultado incorrecto:
y is 505
z is 510
Sin embargo, si reemplazas esto con constantes:
#include <iostream>
const int x = 500;
const int y = x + 5;
int z = y * 2;
int main()
{
std::cout << "y is " << y;
std::cout << "\nz is " << z;
}
Genera el resultado correcto:
y is 505
z is 1010
Esto se debe a que #define
simplemente reemplaza el texto. Debido a que hacer esto puede alterar seriamente el orden de las operaciones, recomendaría usar una variable constante en su lugar.