Penso che la limitazione che hai considerato non sia legata alla semantica (perché qualcosa dovrebbe cambiare se l'inizializzazione fosse definita nello stesso file?), ma piuttosto al modello di compilazione C ++ che, per ragioni di compatibilità con le versioni precedenti, non può essere facilmente modificato perché altrimenti diventerebbe troppo complesso (supportando un nuovo modello di compilazione e quello esistente allo stesso tempo) o non permetterebbe 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 dichiarazioni in un file sorgente includendo 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 IMO un grande vantaggio, cioè che rende il compilatore più facile da implementare. Naturalmente, puoi scrivere qualsiasi cosa nei file inclusi, cioè entrambe le dichiarazioni e le definizioni. È solo una buona pratica inserire le dichiarazioni nei file di intestazione e nelle definizioni nei file .c o .cpp.
D'altra parte, è possibile avere un modello di compilazione in cui il compilatore conosce molto bene se è importare la dichiarazione di un simbolo globale che è definito in un altro modulo , oppure se sta compilando la definizione di un simbolo globale fornito dal modulo corrente . Solo in quest'ultimo caso il compilatore deve mettere questo simbolo (ad esempio una variabile) nella corrente
file oggetto.
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 è dichiarata e inizializzata nello stesso file sorgente.
Quindi puoi avere unità diverse che importano e usare la variabile globale
MyStaticVariable
, ad es. 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à be c in un programma principale m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
Puoi 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 uses in un modulo di programma (ad esempio usa a in b.pas), non include il corrispondente file .pas,
ma cerca un file .gpi, cioè per un file di interfaccia precompilato
(vedi la documentazione ).
Questi file .gpi
vengono generati dal compilatore insieme ai file .o
quando ogni modulo è compilato.
Quindi il simbolo globale MyStaticVariable
viene definito solo una volta nel file oggetto a.o
.
Java funziona in modo simile: quando il compilatore importa una classe A in classe B, guarda 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 al C ++, la ragione per cui in C ++ devi definire membri di dati statici in un file separato è più legata al modello di compilazione C ++ che alle limitazioni imposte dal linker o da altri strumenti usati dal compilatore.
In C ++, importare alcuni simboli significa costruire la loro dichiarazione come parte di
l'attuale unità di compilazione. Questo è molto importante, tra le altre cose,
a causa del modo in cui i modelli sono compilati. Ma questo implica che non puoi / non devi definire alcun simbolo globale (funzioni, variabili, metodi, membri di dati statici) in un file incluso, altrimenti questi simboli potrebbero essere
definizione multipla nei file oggetto compilati.