const estática vs #define

212

¿Es mejor usar static constvars que #definepreprocesador? ¿O tal vez depende del contexto?

¿Cuáles son las ventajas / desventajas de cada método?

Patrice Bernassola
fuente
14
Scott Meyers cubre este tema muy bien y a fondo. Su artículo n. ° 2 en "Tercera edición efectiva de C ++". Se prefieren dos casos especiales (1) const estática dentro de un alcance de clase para constantes específicas de clase; (2) se prefiere el espacio de nombres o el alcance anónimo const sobre #define.
Eric
2
Prefiero Enums. Porque es híbrido de ambos. No ocupa espacio a menos que cree una variable de él. Si solo quiere usarlo como una constante, enum es la mejor opción. Tiene seguridad de tipo en C / C ++ 11 std y también una constante perfecta. #define es de tipo inseguro, const ocupa espacio si el compilador no puede optimizarlo.
siddhusingh 01 de
1
Mi decisión de usar #defineo static const(para cadenas) está impulsada por el aspecto de inicialización (no se mencionó a través de las respuestas a continuación): si la constante se usa solo dentro de una unidad de compilación particular, entonces voy con static const, de lo contrario, uso #define- evite el fiasco de inicialización de orden estático isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak
Si const, constexpro enumalguna variación funciona en su caso, entonces #define
prefiéralo
@MartinDvorak " evitar el fiasco de inicialización de orden estática " ¿Cómo es eso un problema para las constantes?
curioso

Respuestas:

139

Personalmente, odio el preprocesador, así que siempre iría con const .

La principal ventaja de un #define es que no requiere memoria para almacenar en su programa, ya que en realidad solo reemplaza parte del texto con un valor literal. También tiene la ventaja de que no tiene ningún tipo, por lo que puede usarse para cualquier valor entero sin generar advertencias.

Ventajas de "const " s son que pueden ser alcanzados, y pueden usarse en situaciones en las que se necesita pasar un puntero a un objeto.

Sin staticembargo , no sé exactamente a qué te refieres con la parte " ". Si está declarando 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[]) {
...
}
TED
fuente
8
Las constantes de cadena específicamente son una de las que podrían beneficiarse de ser #defined, al menos si pueden usarse como "bloques de construcción" para constantes de cadena más grandes. Vea mi respuesta para un ejemplo.
ANT
62
La #defineventaja de no usar ninguna memoria es inexacta. El "60" en el ejemplo debe almacenarse en algún lugar, independientemente de si es static consto #define. De hecho, he visto compiladores en los que el uso de #define causó un consumo masivo de memoria (solo lectura), y la constante estática no utilizó memoria innecesaria.
Gilad Naor
3
Un #define es como si lo hubiera escrito, por lo que definitivamente no proviene de la memoria.
El Reverendo
27
@theReverend ¿Están los valores literales de alguna manera exentos de consumir recursos de la máquina? No, simplemente pueden usarlos de diferentes maneras, tal vez no aparezca en la pila o el montón, pero en algún momento el programa se carga en la memoria junto con todos los valores compilados en él.
Sqeaky
13
@ gilad-naor, tiene razón en general, pero los enteros pequeños como 60 pueden ser a veces una especie de excepción parcial. Algunos conjuntos de instrucciones tienen la capacidad de codificar enteros o un subconjunto de enteros directamente en la secuencia de instrucciones. Por ejemplo, los MIPs agregan inmediato ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). En este tipo de casos, se podría decir que un entero #definido realmente no usa espacio ya que en el binario compilado ocupa algunos bits de repuesto en las instrucciones que de todos modos tenían que existir.
ahcox
242

Pros y contras entre #defines, constsy (lo que has olvidado) enums, dependiendo del uso:

  1. enums:

    • solo es posible para valores enteros
    • Los problemas de choque de identificador / ámbito adecuadamente manejados bien, particularmente en clases de enumeración de C ++ 11 donde las enumeraciones para enum class Xson ambiguas por el alcanceX::
    • fuertemente tipado, pero con un tamaño int con signo o sin signo suficientemente grande sobre el que no tiene control en C ++ 03 (aunque puede especificar un campo de bits en el que se deben empaquetar si la enumeración es un miembro de struct / class / union), mientras que C ++ 11 se establece de manera predeterminada intpero el programador puede establecerlo explícitamente
    • no puede tomar la dirección: no hay una, ya que los valores de enumeración se sustituyen efectivamente en línea en los puntos de uso
    • Restricciones de uso más fuertes (por ejemplo, incrementos: template <typename T> void f(T t) { cout << ++t; }no se compilará, aunque puede ajustar una enumeración en una clase con un constructor implícito, un operador de conversión y operadores definidos por el usuario)
    • cada tipo de constante tomada de la enumeración adjunta, así que template <typename T> void f(T)obtenga una instanciación distinta cuando pase el mismo valor numérico de enumeraciones diferentes, todas las cuales son distintas de cualquier f(int)instanciación real . El código de objeto de cada función podría ser idéntico (ignorando las compensaciones de dirección), pero no esperaría que un compilador / vinculador elimine las copias innecesarias, aunque puede verificar su compilador / vinculador si le importa.
    • incluso con typeof / decltype, no puedo esperar que numeric_limits proporcione información útil sobre el conjunto de valores y combinaciones significativas (de hecho, las combinaciones "legales" ni siquiera están anotadas en el código fuente, considere enum { A = 1, B = 2 }- es A|B"legal" desde la lógica de un programa ¿perspectiva?)
    • el nombre de tipo de la enumeración puede aparecer en varios lugares en RTTI, mensajes de compilación, etc., posiblemente útil, posiblemente ofuscación
    • no 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, makey otras herramientas de compilación basadas en la marca de tiempo activarán la recompilación del cliente cuando se cambien (¡mal! )

  1. consts:

    • Los problemas de enfrentamiento de identificador / alcance adecuadamente manejados bien
    • tipo fuerte, único, especificado por el usuario
      • puede intentar "escribir" un #defineala #define S std::string("abc"), pero la constante evita la construcción repetida de temporarios distintos en cada punto de uso
    • Complicaciones de una regla de definición
    • puede tomar direcciones, crear referencias constantes a ellas, etc.
    • más similar a un noconst 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 clientes para recoger el cambio

  1. #defines:

    • alcance "global" / más propenso a usos conflictivos, que pueden producir problemas de compilación difíciles de resolver y resultados inesperados en tiempo de ejecución en lugar de mensajes de error razonables; mitigar esto requiere:
      • identificadores largos, oscuros y / o coordinados centralmente, y el acceso a ellos no puede beneficiarse de la coincidencia implícita del espacio de nombres, los alias de espacios de nombres, etc.
      • Si bien la práctica recomendada de triunfo permite que los identificadores de parámetros de plantilla sean letras mayúsculas de un solo carácter (posiblemente seguidas de un número), otro uso de identificadores sin letras minúsculas está convencionalmente reservado y se espera de las definiciones de preprocesador (fuera de la biblioteca del sistema operativo y 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 concursos o enumeraciones existentes a / desde definiciones implica un cambio en la capitalización y, por lo tanto, requiere modificaciones en el código fuente del cliente en lugar de una compilación "simple". (Personalmente, escribo en mayúscula la primera carta de enumeraciones, pero no las contiendas, por lo que también me vería obligado a migrar entre esas dos, tal vez sea hora de repensar eso).
    • Se pueden realizar más operaciones en tiempo de compilación: concatenación literal de cadenas, stringificación (tomando el tamaño de las mismas), concatenación en identificadores
      • desventaja es que dado #define X "x"y algunos Ala uso del cliente "pre" X "post", si usted quiere o necesita para hacer X un tiempo de ejecución modificables variable en lugar de una constante se fuerza ediciones en código de cliente (en lugar de sólo recompilación), mientras que la transición sea más fácil a partir de una const char*o const std::stringdado que ya obliga al usuario a incorporar operaciones de concatenación (por ejemplo, "pre" + X + "post"para string)
    • no se puede usar sizeofdirectamente en un literal numérico definido
    • sin tipo (GCC no advierte si se compara con unsigned)
    • algunas cadenas de compilador / enlazador / depurador pueden no presentar 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 el #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 pre-incluirse, crear "constantes" como las { 1, 2 }que pueden usarse para inicializar matrices, #define MICROSECONDS *1E-6etc. (¡ definitivamente no lo recomiendo!)
    • algunas cosas especiales como __FILE__y__LINE__ se pueden incorporar en la macro sustitución
    • puede probar la existencia y el valor en las #ifdeclaraciones para incluir condicionalmente el código (más potente que un "si" posterior al preprocesamiento, ya que el código no necesita ser compilable si el preprocesador no lo selecciona), use#undef -ine, redefine, etc.
    • el texto sustituido tiene que estar expuesto:
      • en la unidad de traducción que usa, lo que significa que las macros en las bibliotecas para uso del cliente deben estar en el encabezado, por lo que makeotras herramientas de compilación basadas en la marca 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 asegurarse de 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)

Mi opinión personal:

Como regla general, uso constsy considero que es la opción más profesional para uso general (aunque los otros tienen una simplicidad atractiva para este viejo programador perezoso).

Tony Delroy
fuente
1
Impresionante respuesta. Un pequeño inconveniente: a veces uso enumeraciones locales que no están en los encabezados en absoluto solo por la claridad del código, como en máquinas de estado pequeñas y demás. Por lo tanto, no tienen que estar en encabezados, en todo momento.
kert
Los pros y los contras están mezclados, me gustaría ver una tabla de comparación.
Desconocido123
@ Unknown123: siéntase libre de publicar uno, no me importa si arranca cualquier punto que se sienta digno desde aquí. Saludos
Tony Delroy
48

Si esta es una pregunta de C ++ y menciona #definecomo alternativa, entonces se trata de constantes "globales" (es decir, de alcance de archivo), no de miembros de la clase. Cuando se trata de tales constantes en C ++ static constes redundante. En C ++ consttiene un enlace interno por defecto y no tiene sentido declararlos static. Por lo que es realmente acerca de constfrente #define.

Y, finalmente, en C ++ constes preferible. Al menos porque tales constantes están escritas y definidas. Simplemente, no hay razones para preferir #definemás const, aparte de algunas excepciones.

Las constantes de cadena, por cierto, son un ejemplo de dicha excepción. Con #definelas constantes de cadena d se puede usar la función de concatenación en tiempo de compilación de 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: De nuevo, por si acaso, cuando alguien menciona static constcomo una alternativa #define, generalmente significa que está hablando de C, no de C ++. Me pregunto si esta pregunta está etiquetada correctamente ...

Hormiga
fuente
1
" simplemente no hay razones para preferir #definir " ¿Sobre qué? ¿Variables estáticas definidas en un archivo de encabezado?
curioso
9

#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;
}

Da como resultado un resultado incorrecto:

y is 505
z is 510

Sin embargo, si reemplaza 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;
}

Produce el resultado correcto:

y is 505
z is 1010

Esto se debe a que #definesimplemente reemplaza el texto. Debido a que esto puede arruinar seriamente el orden de las operaciones, recomendaría usar una variable constante.

Juniorized
fuente
1
Tuve un resultado inesperado diferente: ytenía el valor 5500, una concatenación little endian de xy 5.
Códigos con Hammer
5

Usar una constante estática es como usar cualquier otra variable constante en su código. Esto significa que puede rastrear el origen de la información, en lugar de un #define que simplemente se reemplazará en el código en el proceso de compilación previa.

Es posible que desee echar un vistazo a C ++ FAQ Lite para esta pregunta: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7

Percutio
fuente
4
  • Se escribe una constante estática (tiene un tipo) y el compilador puede verificar su validez, redefinición, etc.
  • un #define puede redifinarse indefinido lo que sea.

Por lo general, debe preferir const estáticos. No tiene desventaja. El procesador debe usarse principalmente para la compilación condicional (y, a veces, para trucos realmente sucios).

ADAIR SUAVE ROJA
fuente
3

#defineNo se recomienda definir constantes utilizando la directiva de preprocesador para aplicar no solo en C++, sino también en C. Estas constantes no tendrán el tipo. Incluso en Cse propuso utilizar constpara constantes.


fuente
2

Ver aquí: static const vs define

Por lo general, una declaración constante (tenga en cuenta que no necesita ser estática) es el camino a seguir

ennuikiller
fuente
2

Siempre prefiera usar las funciones del lenguaje sobre algunas herramientas adicionales como el preprocesador.

ES.31: No use macros para constantes o "funciones"

Las macros son una fuente importante de errores. Las macros no obedecen las reglas habituales de alcance y tipo. Las macros no obedecen las reglas usuales para pasar argumentos. Las macros aseguran que el lector humano vea algo diferente de lo que ve el compilador. Las macros complican la construcción de herramientas.

De las pautas básicas de C ++

Hitokage
fuente
0

Si está definiendo una constante para ser compartida entre todas las instancias de la clase, use const estática. Si la constante es específica para cada instancia, solo use const (pero tenga en cuenta que todos los constructores de la clase deben inicializar esta variable miembro const en la lista de inicialización).

snr
fuente