Penso che la limitazione che hai preso in considerazione non sia correlata alla semantica (perché qualcosa dovrebbe cambiare se l'inizializzazione fosse definita nello stesso file?) Ma piuttosto al modello di compilazione C ++ che, per motivi di compatibilità con le versioni precedenti, non può essere facilmente modificato perché o diventare troppo complesso (supportando un nuovo modello di compilazione e quello esistente contemporaneamente) o non consentirebbe di compilare il codice esistente (introducendo un nuovo modello di compilazione e rilasciando quello esistente).
Il modello di compilazione C ++ deriva da quello di C, in cui si importano le dichiarazioni in un file di origine includendo i file (di intestazione). In questo modo, il compilatore vede esattamente un grande file sorgente, contenente tutti i file inclusi e tutti i file inclusi da quei file, in modo ricorsivo. Questo ha un grande vantaggio dell'IMO, ovvero che semplifica l'implementazione del compilatore. Ovviamente, puoi scrivere qualsiasi cosa nei file inclusi, cioè sia dichiarazioni che definizioni. È buona norma inserire le dichiarazioni nei file di intestazione e le definizioni nei file .c o .cpp.
D'altra parte, è possibile avere un modello di compilazione in cui il compilatore sappia molto bene se sta importando la dichiarazione di un simbolo globale che è definita in un altro modulo o se sta compilando la definizione di un simbolo globale fornita da il modulo corrente . Solo in quest'ultimo caso il compilatore deve inserire questo simbolo (ad es. Una variabile) nel file oggetto corrente.
Ad esempio, in GNU Pascal puoi scrivere un'unità a
in un file a.pas
come questo:
unit a;
interface
var MyStaticVariable: Integer;
implementation
begin
MyStaticVariable := 0
end.
dove la variabile globale viene dichiarata e inizializzata nello stesso file di origine.
Quindi puoi avere diverse unità che importano a e usano la variabile globale
MyStaticVariable
, ad esempio un'unità b ( b.pas
):
unit b;
interface
uses a;
procedure PrintB;
implementation
procedure PrintB;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
e un'unità c ( c.pas
):
unit c;
interface
uses a;
procedure PrintC;
implementation
procedure PrintC;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
Finalmente puoi usare le unità bec in un programma principale m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
È possibile compilare questi file separatamente:
$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas
e quindi produrre un eseguibile con:
$ gpc -o m m.o a.o b.o c.o
ed eseguilo:
$ ./m
1
2
3
Il trucco qui è che quando il compilatore vede una direttiva usi in un modulo di programma (ad esempio usa a in b.pas), non include il file .pas corrispondente, ma cerca un file .gpi, ovvero un pre-compilato file di interfaccia (consultare la documentazione ). Questi .gpi
file vengono generati dal compilatore insieme ai .o
file quando viene compilato ciascun modulo. Quindi il simbolo globale MyStaticVariable
viene definito una sola volta nel file oggetto a.o
.
Java funziona in modo simile: quando quindi il compilatore importa una classe A in classe B, cerca il file di classe per A e non ha bisogno del file A.java
. Quindi tutte le definizioni e le inizializzazioni per la classe A possono essere inserite in un file sorgente.
Tornando a C ++, il motivo per cui in C ++ è necessario definire membri di dati statici in un file separato è più correlato al modello di compilazione C ++ che alle limitazioni imposte dal linker o da altri strumenti utilizzati dal compilatore. In C ++, importare alcuni simboli significa costruire la loro dichiarazione come parte dell'attuale unità di compilazione. Questo è molto importante, tra le altre cose, a causa del modo in cui i modelli vengono compilati. Ciò implica che non è possibile / non si devono definire simboli globali (funzioni, variabili, metodi, membri di dati statici) in un file incluso, altrimenti questi simboli potrebbero essere definiti in modo multiplo nei file oggetto compilati.