Myślę, że rozważane ograniczenie nie jest związane z semantyką (dlaczego coś miałoby się zmieniać, gdyby inicjalizacja została zdefiniowana w tym samym pliku?), Ale raczej z modelem kompilacji C ++, którego ze względu na kompatybilność wsteczną nie można łatwo zmienić, ponieważ albo stają się zbyt skomplikowane (obsługując jednocześnie nowy model kompilacji i istniejący) lub nie pozwalają na kompilację istniejącego kodu (poprzez wprowadzenie nowego modelu kompilacji i usunięcie istniejącego).
Model kompilacji C ++ wywodzi się z modelu C, w którym importujesz deklaracje do pliku źródłowego, włączając pliki (nagłówkowe). W ten sposób kompilator widzi dokładnie jeden duży plik źródłowy, zawierający wszystkie zawarte pliki i wszystkie pliki z tych plików, rekurencyjnie. Ma to jedną wielką zaletę IMO, a mianowicie to, że ułatwia wdrożenie kompilatora. Oczywiście w zapisanych plikach można pisać wszystko, tzn. Deklaracje i definicje. Dobrą praktyką jest umieszczanie deklaracji w plikach nagłówkowych i definicji w plikach .c lub .cpp.
Z drugiej strony możliwe jest posiadanie modelu kompilacji, w którym kompilator doskonale wie, czy importuje deklarację symbolu globalnego zdefiniowanego w innym module , czy też kompiluje definicję symbolu globalnego dostarczoną przez obecny moduł . Tylko w tym drugim przypadku kompilator musi umieścić ten symbol (np. Zmienną) w bieżącym pliku obiektowym.
Na przykład w GNU Pascal możesz zapisać jednostkę a
w pliku a.pas
takim jak ten:
unit a;
interface
var MyStaticVariable: Integer;
implementation
begin
MyStaticVariable := 0
end.
gdzie zmienna globalna jest zadeklarowana i zainicjowana w tym samym pliku źródłowym.
Następnie możesz mieć różne jednostki, które importują a i używają zmiennej globalnej
MyStaticVariable
, np. Jednostkę b ( b.pas
):
unit b;
interface
uses a;
procedure PrintB;
implementation
procedure PrintB;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
oraz jednostka c ( c.pas
):
unit c;
interface
uses a;
procedure PrintC;
implementation
procedure PrintC;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
Wreszcie możesz użyć jednostek b i c w głównym programie m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
Możesz skompilować te pliki osobno:
$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas
a następnie utworzyć plik wykonywalny z:
$ gpc -o m m.o a.o b.o c.o
i uruchom to:
$ ./m
1
2
3
Sztuczka polega na tym, że gdy kompilator widzi dyrektywę use w module programu (np. Używa a w b.pas), nie zawiera odpowiedniego pliku .pas, ale szuka pliku .gpi, czyli wstępnie skompilowanej plik interfejsu (patrz dokumentacja ). .gpi
Pliki te są generowane przez kompilator wraz z .o
plikami podczas kompilacji każdego modułu. Tak więc symbol globalny MyStaticVariable
jest zdefiniowany tylko raz w pliku obiektowym a.o
.
Java działa w podobny sposób: kiedy następnie kompilator importuje klasę A do klasy B, szuka pliku klasy dla A i nie potrzebuje go A.java
. Tak więc wszystkie definicje i inicjalizacje dla klasy A można umieścić w jednym pliku źródłowym.
Wracając do C ++, powód, dla którego w C ++ musisz zdefiniować statyczne elementy danych w osobnym pliku, jest bardziej związany z modelem kompilacji C ++ niż z ograniczeniami narzuconymi przez linker lub inne narzędzia używane przez kompilator. W C ++ import niektórych symboli oznacza zbudowanie ich deklaracji jako części bieżącej jednostki kompilacji. Jest to bardzo ważne między innymi ze względu na sposób kompilowania szablonów. Oznacza to jednak, że nie można / nie należy definiować żadnych globalnych symboli (funkcji, zmiennych, metod, elementów danych statycznych) w dołączonym pliku, w przeciwnym razie symbole te mogłyby zostać wielokrotnie zdefiniowane w skompilowanych plikach obiektowych.