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 archivo a.pas
como este:
unit a;
interface
var MyStaticVariable: Integer;
implementation
begin
MyStaticVariable := 0
end.
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
):
unit b;
interface
uses a;
procedure PrintB;
implementation
procedure PrintB;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
y una unidad c ( c.pas
):
unit c;
interface
uses a;
procedure PrintC;
implementation
procedure PrintC;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
Finalmente, puede usar las unidades byc en un programa principal m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
Puede compilar estos archivos por separado:
$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas
y luego produce un ejecutable con:
$ gpc -o m m.o a.o b.o c.o
y ejecutarlo:
$ ./m
1
2
3
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 global MyStaticVariable
solo se define una vez en el archivo objeto a.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.