class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
No veo la necesidad de haber A::x
definido por separado en un archivo .cpp (o el mismo archivo para plantillas). ¿Por qué no se puede A::x
declarar y definir al mismo tiempo?
¿Ha sido prohibido por razones históricas?
Mi pregunta principal es, ¿afectará alguna funcionalidad si static
los miembros de datos se declararon / definieron al mismo tiempo (igual que Java )?
c++
data
language-features
grammar
static-access
iammilind
fuente
fuente
inline static int x[] = {1, 2, 3};
. Ver en.cppreference.com/w/cpp/language/static#Static_data_membersRespuestas:
Creo que la limitación que ha considerado no está relacionada con la semántica (¿por qué debería cambiar algo si la inicialización se definió en el mismo archivo?) Sino con el modelo de compilación de C ++ que, por razones de compatibilidad con versiones anteriores, no se puede cambiar fácilmente porque o se vuelve demasiado complejo (admite un nuevo modelo de compilación y el existente al mismo tiempo) o no permitiría compilar el código existente (al introducir un nuevo modelo de compilación y descartar el existente).
El modelo de compilación C ++ se deriva del de C, en el que importa declaraciones en un archivo fuente al incluir archivos (encabezados). De esta manera, el compilador ve exactamente un gran archivo fuente, que contiene todos los archivos incluidos, y todos los archivos incluidos de esos archivos, de forma recursiva. Esto tiene una gran ventaja para IMO, es decir, hace que el compilador sea más fácil de implementar. Por supuesto, puede escribir cualquier cosa en los archivos incluidos, es decir, declaraciones y definiciones. Es una buena práctica colocar declaraciones en archivos de encabezado y definiciones en archivos .c o .cpp.
Por otro lado, es posible tener un modelo de compilación en el que el compilador sabe muy bien si está importando la declaración de un símbolo global que se define en otro módulo , o si está compilando la definición de un símbolo global proporcionada por El módulo actual . Solo en el último caso, el compilador debe poner este símbolo (por ejemplo, una variable) en el archivo de objeto actual.
Por ejemplo, en GNU Pascal puede escribir una unidad
a
en un archivoa.pas
como este:donde la variable global se declara e inicializa en el mismo archivo fuente.
Entonces puede tener diferentes unidades que importen
MyStaticVariable
ay usen la variable global , por ejemplo, una unidad b (b.pas
):y una unidad c (
c.pas
):Finalmente, puede usar las unidades byc en un programa principal
m.pas
:Puede compilar estos archivos por separado:
y luego produce un ejecutable con:
y ejecutarlo:
El truco aquí es que cuando el compilador ve una directiva de usos en un módulo de programa (por ejemplo, usa a en b.pas), no incluye el archivo .pas correspondiente, sino que busca un archivo .gpi, es decir, un precompilado archivo de interfaz (ver la documentación ). El
.gpi
compilador genera estos archivos junto con los.o
archivos cuando se compila cada módulo. Por lo tanto, el símbolo globalMyStaticVariable
solo se define una vez en el archivo objetoa.o
.Java funciona de manera similar: cuando el compilador importa una clase A a la clase B, mira el archivo de clase para A y no necesita el archivo
A.java
. Por lo tanto, todas las definiciones e inicializaciones para la clase A se pueden colocar en un archivo fuente.Volviendo a C ++, la razón por la cual en C ++ tiene que definir miembros de datos estáticos en un archivo separado está más relacionada con el modelo de compilación de C ++ que con las limitaciones impuestas por el enlazador u otras herramientas utilizadas por el compilador. En C ++, importar algunos símbolos significa construir su declaración como parte de la unidad de compilación actual. Esto es muy importante, entre otras cosas, debido a la forma en que se compilan las plantillas. Pero esto implica que no puede / no debe definir ningún símbolo global (funciones, variables, métodos, miembros de datos estáticos) en un archivo incluido, de lo contrario, estos símbolos podrían definirse de manera múltiple en los archivos de objetos compilados.
fuente
Como los miembros estáticos se comparten entre TODAS las instancias de una clase, deben definirse en un solo lugar. Realmente, son variables globales con algunas restricciones de acceso.
Si intenta definirlos en el encabezado, se definirán en cada módulo que incluya ese encabezado, y obtendrá errores durante el enlace ya que encuentra todas las definiciones duplicadas.
Sí, este es al menos en parte un problema histórico que data de cfront; se podría escribir un compilador que creara una especie de "static_members_of_everything.cpp" oculto y se vincule a eso. Sin embargo, rompería la compatibilidad con versiones anteriores y no habría ningún beneficio real al hacerlo.
fuente
static
variables se declaran / definen en el mismo lugar (como Java), ¿qué puede salir mal?static
miembrostemplate
? Se permiten en todos los archivos de encabezado, ya que deben estar visibles. No estoy disputando esta respuesta, pero tampoco coincide con mi pregunta.La razón probable de esto es que esto mantiene el lenguaje C ++ implementable en entornos donde el archivo de objeto y el modelo de vinculación no admite la fusión de múltiples definiciones de múltiples archivos de objeto.
Una declaración de clase (llamada declaración por buenas razones) se extrae en varias unidades de traducción. Si la declaración contuviera definiciones para variables estáticas, terminaría con múltiples definiciones en múltiples unidades de traducción (Y recuerde, estos nombres tienen un enlace externo).
Esa situación es posible, pero requiere que el enlazador maneje múltiples definiciones sin quejarse.
(Y tenga en cuenta que esto entra en conflicto con la Regla de una definición, a menos que se pueda hacer de acuerdo con el tipo de símbolo o en qué tipo de sección se coloca).
fuente
Hay una gran diferencia entre C ++ y Java.
Java opera en su propia máquina virtual que crea todo en su propio entorno de tiempo de ejecución. Si una definición se ve más de una vez, simplemente actuará sobre el mismo objeto que el entorno de ejecución conoce en última instancia.
En C ++ no hay un "propietario del conocimiento final": C ++, C, Fortran Pascal, etc. son todos "traductores" de un código fuente (archivo CPP) a un formato intermedio (el archivo OBJ o ".o", dependiendo de OS) donde las declaraciones se traducen en instrucciones de máquina y los nombres se convierten en direcciones indirectas mediadas por una tabla de símbolos.
El compilador no crea un programa, sino otro programa (el "enlazador"), que une todos los OBJ-s (sin importar el idioma del que proceden) volviendo a señalar todas las direcciones que están hacia símbolos, hacia sus definición efectiva
Por cierto, el enlazador funciona, una definición (lo que crea el espacio físico para una variable) debe ser única.
Tenga en cuenta que C ++ no se vincula por sí mismo, y que el vinculador no es emitido por las especificaciones de C ++: el vinculador existe debido a la forma en que se construyen los módulos del sistema operativo (generalmente en C y ASM). C ++ tiene que usarlo como está.
Ahora: un archivo de encabezado es algo para "pegar" en varios archivos CPP. Cada archivo CPP se traduce independientemente de todos los demás. Un compilador que traduce diferentes archivos CPP, todos los que reciben una misma definición colocará el " código de creación " para el objeto definido en todos los OBJ resultantes.
El compilador no sabe (y nunca sabrá) si todos esos OBJ alguna vez se usarán juntos para formar un solo programa o por separado para formar diferentes programas independientes.
El vinculador no sabe cómo y por qué existen las definiciones y de dónde provienen (ni siquiera sabe acerca de C ++: cada "lenguaje estático" puede producir definiciones y referencias para vincular). Simplemente sabe que hay referencias a un "símbolo" dado que está "definido" en una dirección resultante dada.
Si hay varias definiciones (no confunda las definiciones con las referencias) para un símbolo dado, el enlazador no tiene conocimiento (siendo independiente del lenguaje) sobre qué hacer con ellos.
Es como fusionar varias ciudades para formar una gran ciudad: si te encuentras con dos " Time square " y un número de personas que vienen de fuera pidiendo ir a " Time square ", no puedes decidir sobre una base técnica pura. (sin ningún conocimiento sobre la política que asignó esos nombres y se encargará de administrarlos) en qué lugar exacto enviarlos.
fuente
Es necesario porque de lo contrario el compilador no sabe dónde colocar la variable. Cada archivo cpp se compila individualmente y no conoce el otro. El enlazador resuelve variables, funciones, etc. Personalmente, no veo cuál es la diferencia entre los miembros vtable y los estáticos (no tenemos que elegir en qué archivo se definen los vtable).
Principalmente supongo que es más fácil para los escritores de compiladores implementarlo de esa manera. Existen variables estáticas fuera de la clase / estructura y quizás por razones de coherencia o porque sería "más fácil de implementar" para los escritores de compiladores que definieron esa restricción en los estándares.
fuente
Creo que encontré la razón. La definición de
static
variable en un espacio separado permite inicializarla a cualquier valor. Si no se inicializa, el valor predeterminado será 0.Antes de C ++ 11, la inicialización en clase no estaba permitida en C ++. Entonces uno no puede escribir como:
Por lo tanto, ahora para inicializar la variable, uno debe escribirla fuera de la clase como:
Como se discutió en otras respuestas también,
int X::i
ahora es global y la declaración global en muchos archivos causa un error de enlace de múltiples símbolos.Por lo tanto, uno tiene que declarar una
static
variable de clase dentro de una unidad de traducción separada. Sin embargo, aún se puede argumentar que la siguiente forma debería indicar al compilador que no cree múltiples símbolosfuente
A :: x es solo una variable global pero un espacio de nombres para A, y con restricciones de acceso.
Alguien todavía tiene que declararlo, como cualquier otra variable global, y eso incluso puede hacerse en un proyecto que está estáticamente vinculado al proyecto que contiene el resto del código A.
Llamaría a todos estos mal diseño, pero hay algunas características que puede explotar de esta manera:
el orden de llamada del constructor ... No es importante para un int, pero para un miembro más complejo que tal vez accede a otras variables estáticas o globales, puede ser crítico.
el inicializador estático: puede dejar que un cliente decida a qué A :: x se debe inicializar.
en c ++ y c, debido a que tiene acceso completo a la memoria a través de punteros, la ubicación física de las variables es significativa. Hay cosas muy traviesas que puede explotar en función de dónde se encuentra una variable en un objeto de enlace.
Dudo que estos sean "por qué" ha surgido esta situación. Probablemente sea solo una evolución de C convirtiéndose en C ++, y un problema de compatibilidad con versiones anteriores que le impide cambiar el idioma ahora.
fuente