c++ - usar - variable static java
Variable miembro estática C++ y su inicialización (5)
Para las variables miembro estáticas en la clase C ++, la inicialización se realiza fuera de la clase. ¿Me pregunto porque? Cualquier razonamiento / restricción lógica para esto? ¿O es una implementación puramente heredada, que el estándar no quiere corregir?
Creo que tener la inicialización en la clase es más "intuitivo" y menos confuso. También da la sensación tanto estática como global de la variable. Por ejemplo, si ve el miembro static const.
Creo que la razón principal para realizar la inicialización fuera del bloque de class
es permitir la inicialización con valores devueltos de otras funciones de miembros de la clase. Si quisieras inicializar a::var
con b::some_static_fn()
necesitarías asegurarte de que cada archivo .cpp
que incluye ah
incluya bh
primero. Sería un desastre, especialmente cuando (tarde o temprano) te encuentras con una referencia circular que solo puedes resolver con una interface
innecesaria. El mismo problema es la razón principal para tener implementaciones de funciones miembro de clase en un archivo .cpp
lugar de poner todo en su clase principal '' .h
.
Al menos con las funciones de miembro, tiene la opción de implementarlas en el encabezado. Con las variables debe hacer la inicialización en un archivo .cpp. No estoy del todo de acuerdo con la limitación, y tampoco creo que haya una buena razón para ello.
En C ++ desde el principio de los tiempos, la presencia de un inicializador era un atributo exclusivo de la definición del objeto, es decir, una declaración con un inicializador siempre es una definición (casi siempre).
Como debe saber, cada objeto externo utilizado en el programa C ++ debe definirse una sola vez en una sola unidad de traducción. Permitir inicializadores en clase para objetos estáticos iría inmediatamente en contra de esta convención: los inicializadores irían a archivos de encabezado (donde normalmente residen las definiciones de clase) y generarían varias definiciones del mismo objeto estático (uno para cada unidad de traducción que incluye el archivo de encabezado) ) Esto es, por supuesto, inaceptable. Por esta razón, el enfoque de declaración para miembros de clase estáticos se deja perfectamente "tradicional": usted solo lo declara en el archivo de encabezado (es decir, no se permite inicializador) y luego lo define en una unidad de traducción de su elección (posiblemente con un inicializador )
Se hizo una excepción de esta regla para los miembros de clase estática const de tipos integrales o enum, porque tales entradas pueden ser para expresiones constantes integrales (ICE). La idea principal de los ICE es que se evalúan en tiempo de compilación y, por lo tanto, no dependen de las definiciones de los objetos involucrados. Por eso esta excepción fue posible para los tipos integral o enum. Pero para otros tipos simplemente contradiría los principios básicos de declaración / definición de C ++.
Es por la forma en que se compila el código. Si tuviera que inicializarlo en la clase, que a menudo está en el encabezado, cada vez que se incluye el encabezado obtendría una instancia de la variable estática. Definitivamente esta no es la intención. Tenerlo inicializado fuera de la clase le da la posibilidad de inicializarlo en el archivo cpp.
Fundamentalmente, esto se debe a que los miembros estáticos deben definirse en exactamente una unidad de traducción, para no violar la Regla de una sola definición . Si el lenguaje fuera para permitir algo como:
struct Gizmo
{
static string name = "Foo";
};
entonces el name
se definiría en cada unidad de traducción que #include
este archivo de encabezado.
C ++ le permite definir miembros estáticos integrales dentro de la declaración, pero aún debe incluir una definición dentro de una sola unidad de traducción, pero esto es solo un atajo o azúcar sintáctico. Entonces, esto está permitido:
struct Gizmo
{
static const int count = 42;
};
Siempre que a) la expresión sea const
integral o tipo de enumeración, b) la expresión se pueda evaluar en tiempo de compilación, yc) todavía haya una definición en alguna parte que no viole la regla de una definición:
archivo: gizmo.cpp
#include "gizmo.h"
const int Gizmo::count;
La Sección 9.4.2, Miembros de datos estáticos, del estándar C ++ establece:
Si un miembro de datos
static
es de tipoconst
integral oconst
enumeration, su declaración en la definición de clase puede especificar un const-initializer que será una expresión constante integral.
Por lo tanto, es posible que el valor de un miembro de datos estáticos se incluya "dentro de la clase" (por lo que supongo que se refiere a la declaración de la clase). Sin embargo, el tipo del miembro de datos estáticos debe ser un tipo de enumeración const
integral o const
. La razón por la cual los valores de los miembros de datos estáticos de otros tipos no pueden especificarse dentro de la declaración de clase es que probablemente se requiera una inicialización no trivial (es decir, que un constructor debe ejecutarse).
Imagine si lo siguiente fuera legal:
// my_class.hpp
#include <string>
class my_class
{
public:
static std::string str = "static std::string";
//...
Cada archivo de objeto correspondiente a los archivos CPP que incluyen este encabezado no solo tendrá una copia del espacio de almacenamiento para my_class::str
(que consta de bytes sizeof(std::string)
), sino también una "sección ctor" que llama al std::string
constructor tomando una C-string. Cada copia del espacio de almacenamiento para my_class::str
se identificaría con una etiqueta común, por lo que un enlazador podría combinar teóricamente todas las copias del espacio de almacenamiento en una sola. Sin embargo, un enlazador no podría aislar todas las copias del código del constructor dentro de las secciones del ctor de los archivos del objeto. Sería como pedirle al enlazador que elimine todo el código para inicializar str
en la compilación de lo siguiente:
std::map<std::string, std::string> map;
std::vector<int> vec;
std::string str = "test";
int c = 99;
my_class mc;
std::string str2 = "test2";
EDITAR Es instructivo mirar la salida del ensamblador de g ++ para el siguiente código:
// SO4547660.cpp
#include <string>
class my_class
{
public:
static std::string str;
};
std::string my_class::str = "static std::string";
El código de ensamblado se puede obtener ejecutando:
g++ -S SO4547660.cpp
Al SO4547660.s
archivo SO4547660.s
que generó g ++, puede ver que hay un gran código para un archivo fuente tan pequeño.
__ZN8my_class3strE
es la etiqueta del espacio de almacenamiento para my_class::str
. También existe el origen de ensamblado de una función __static_initialization_and_destruction_0(int, int)
, que tiene la etiqueta __Z41__static_initialization_and_destruction_0ii
. Esa función es especial para g ++ pero solo debes saber que g ++ se asegurará de que se llame antes de que se ejecute cualquier código que no sea inicializador. Observe que la implementación de esta función llama a __ZNSsC1EPKcRKSaIcE
. Este es el símbolo mutilado para std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&)
.
Volviendo al ejemplo hipotético anterior y usando estos detalles, cada archivo de objeto correspondiente a un archivo CPP que incluye my_class.hpp
tendría la etiqueta __ZN8my_class3strE
para sizeof(std::string)
bytes, así como el código de ensamblado para llamar a __ZNSsC1EPKcRKSaIcE
dentro de su implementación de la función __static_initialization_and_destruction_0(int, int)
. El enlazador puede fusionar fácilmente todas las ocurrencias de __ZN8my_class3strE
, pero posiblemente no puede aislar el código que llama a __ZNSsC1EPKcRKSaIcE
dentro de la implementación del archivo del objeto de __static_initialization_and_destruction_0(int, int)
.