Quiero definir una constante en C ++ para que sea visible en varios archivos fuente. Puedo imaginar las siguientes formas de definirlo en un archivo de encabezado:
#define GLOBAL_CONST_VAR 0xFF
int GLOBAL_CONST_VAR = 0xFF;
- Alguna función recupera el valor (p
int get_GLOBAL_CONST_VAR()
. Ej. ) enum { GLOBAL_CONST_VAR = 0xFF; }
const int GLOBAL_CONST_VAR = 0xFF;
extern const int GLOBAL_CONST_VAR;
y en un archivo fuenteconst int GLOBAL_CONST_VAR = 0xFF;
Opción (1): definitivamente no es la opción que le gustaría usar
Opción (2): definir la instancia de la variable en cada archivo de objeto utilizando el archivo de encabezado
Opción (3): la OMI ha terminado con la matanza en la mayoría de los casos
Opción (4): en muchos casos, tal vez no sea bueno ya que enum no tiene un tipo concreto (C ++ 0X agregará la posibilidad de definir el tipo)
Entonces, en la mayoría de los casos, necesito elegir entre (5) y (6). Mis preguntas:
- ¿Qué prefieres (5) o (6)?
- ¿Por qué (5) está bien y (2) no?
Respuestas:
(5) dice exactamente lo que quiere decir. Además, permite que el compilador lo optimice la mayor parte del tiempo. (6) por otro lado no permitirá que el compilador lo optimice porque el compilador no sabe si lo cambiará eventualmente o no.
fuente
extern const int ...
yconst int ...
ambos son constantes, ¿no es así?Definitivamente opte por la opción 5: es de tipo seguro y permite que el compilador optimice (no tome la dirección de esa variable :) Además, si está en un encabezado, péguelo en un espacio de nombres para evitar contaminar el alcance global:
// header.hpp namespace constants { const int GLOBAL_CONST_VAR = 0xFF; // ... other related constants } // namespace constants // source.cpp - use it #include <header.hpp> int value = constants::GLOBAL_CONST_VAR;
fuente
header.hpp
en varios archivos fuente.constexpr
enumeraciones mecanografiadas para cosas así.(5) es "mejor" que (6) porque se define
GLOBAL_CONST_VAR
como una expresión constante integral (ICE) en todas las unidades de traducción. Por ejemplo, podrá usarlo como tamaño de matriz y como etiqueta de caso en todas las unidades de traducción. En el caso de (6)GLOBAL_CONST_VAR
habrá un ICE solo en esa unidad de traducción donde está definido y solo después del punto de definición. En otras unidades de traducción, no funcionará como ICE.Sin embargo, tenga en cuenta que (5) proporciona
GLOBAL_CONST_VAR
un enlace interno, lo que significa que la "identidad de dirección" deGLOBAL_CONST_VAR
será diferente en cada unidad de traducción, es decir,&GLOBAL_CONST_VAR
le dará un valor de puntero diferente en cada unidad de traducción. En la mayoría de los casos de uso, esto no importa, pero si necesita un objeto constante que tenga una "identidad de dirección" global consistente, entonces tendrá que ir con (6), sacrificando la ICE-ness de la constante en el proceso.Además, cuando el ICE-ness de la constante no es un problema (no es un tipo integral) y el tamaño del tipo crece (no es un tipo escalar), entonces (6) generalmente se convierte en un enfoque mejor que (5).
(2) no está bien porque
GLOBAL_CONST_VAR
en (2) tiene un enlace externo de forma predeterminada. Si lo coloca en el archivo de encabezado, generalmente terminará con múltiples definiciones deGLOBAL_CONST_VAR
, lo cual es un error.const
los objetos en C ++ tienen un enlace interno de forma predeterminada, por lo que (5) funciona (y por eso, como dije anteriormente, obtienes unaGLOBAL_CONST_VAR
unidad de traducción separada e independiente ).A partir de C ++ 17, tiene la opción de declarar
inline extern const int GLOBAL_CONST_VAR = 0xFF;
en un archivo de encabezado. Esto le da un ICE en todas las unidades de traducción (como en el método (5)) al mismo tiempo que mantiene la identidad de dirección global de
GLOBAL_CONST_VAR
- en todas las unidades de traducción tendrá la misma dirección.fuente
Si usa C ++ 11 o posterior, intente usar constantes en tiempo de compilación:
constexpr int GLOBAL_CONST_VAR{ 0xff };
fuente
Si va a ser una constante, entonces debes marcarla como una constante; por eso, en mi opinión, 2 es malo.
El compilador puede usar la naturaleza constante del valor para expandir algunas de las matemáticas y, de hecho, otras operaciones que usan el valor.
La elección entre 5 y 6 - hmm; 5 simplemente se siente mejor para mí.
En 6) el valor se separa innecesariamente de su declaración.
Por lo general, tendría uno o más de estos encabezados que solo definen constantes, etc.dentro de ellos, y luego ninguna otra cosa 'inteligente': bonitos encabezados livianos que se pueden incluir fácilmente en cualquier lugar.
fuente
Para responder a su segunda pregunta:
(2) es ilegal porque viola la regla de una definición. Define
GLOBAL_CONST_VAR
en cada archivo dónde se incluye, es decir, más de una vez. (5) es legal porque no está sujeto a la regla de una definición. Cada unoGLOBAL_CONST_VAR
es una definición separada, local para ese archivo donde está incluido. Todas esas definiciones comparten el mismo nombre y valor, por supuesto, pero sus direcciones podrían diferir.fuente
C ++ 17
inline
variablesEsta impresionante característica de C ++ 17 nos permite:
constexpr
: ¿Cómo declarar constexpr extern?main.cpp
#include <cassert> #include "notmain.hpp" int main() { // Both files see the same memory address. assert(¬main_i == notmain_func()); assert(notmain_i == 42); }
notmain.hpp
#ifndef NOTMAIN_HPP #define NOTMAIN_HPP inline constexpr int notmain_i = 42; const int* notmain_func(); #endif
notmain.cpp
#include "notmain.hpp" const int* notmain_func() { return ¬main_i; }
Compila y ejecuta:
g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o ./main
GitHub aguas arriba .
Consulte también: ¿Cómo funcionan las variables en línea?
Estándar C ++ en variables en línea
El estándar C ++ garantiza que las direcciones serán las mismas. Borrador estándar 10.1.6 de C ++ 17 N4659 "El especificador en línea":
cppreference https://en.cppreference.com/w/cpp/language/inline explica que si
static
no se proporciona, entonces tiene un enlace externo.Implementación de variables en línea
Podemos observar cómo se implementa con:
que contiene:
main.o: U _GLOBAL_OFFSET_TABLE_ U _Z12notmain_funcv 0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__ U __assert_fail 0000000000000000 T main 0000000000000000 u notmain_i notmain.o: 0000000000000000 T _Z12notmain_funcv 0000000000000000 u notmain_i
y
man nm
dice sobreu
:por lo que vemos que hay una extensión ELF dedicada para esto.
Probado en GCC 7.4.0, Ubuntu 18.04.
fuente
const int GLOBAL_CONST_VAR = 0xFF;
porque es una constante!
fuente
Depende de sus requisitos. (5) es el mejor para el uso más normal, pero a menudo resulta en la ocupación constante de espacio de almacenamiento en cada archivo de objeto. (6) puede evitar esto en situaciones en las que es importante.
(4) también es una opción decente si su prioridad es garantizar que nunca se asigne espacio de almacenamiento, pero, por supuesto, solo funciona para constantes integrales.
fuente
#define GLOBAL_CONST_VAR 0xFF // this is C code not C++ int GLOBAL_CONST_VAR = 0xFF; // it is not constant and maybe not compilled Some function returing the value (e.g. int get_LOBAL_CONST_VAR()) // maybe but exists better desision enum { LOBAL_CONST_VAR = 0xFF; } // not needed, endeed, for only one constant (enum elms is a simple int, but with secial enumeration) const int GLOBAL_CONST_VAR = 0xFF; // it is the best extern const int GLOBAL_CONST_VAR; //some compiller doesn't understand this
fuente