Saya pikir batasan yang Anda pertimbangkan tidak terkait dengan semantik (mengapa sesuatu harus berubah jika inisialisasi didefinisikan dalam file yang sama?) Tetapi lebih kepada model kompilasi C ++ yang, karena alasan kompatibilitas mundur, tidak dapat dengan mudah diubah karena akan menjadi terlalu kompleks (mendukung model kompilasi baru dan yang ada pada saat yang sama) atau tidak akan mengizinkan untuk mengkompilasi kode yang ada (dengan memperkenalkan model kompilasi baru dan menjatuhkan yang ada).
Model kompilasi C ++ berasal dari C, di mana Anda mengimpor deklarasi ke file sumber dengan menyertakan file (header). Dengan cara ini, kompiler melihat persis satu file sumber besar, yang berisi semua file yang disertakan, dan semua file yang disertakan dari file-file itu, secara rekursif. Ini memiliki IMO satu keuntungan besar, yaitu membuat kompiler lebih mudah diimplementasikan. Tentu saja, Anda dapat menulis apa pun di file yang disertakan, yaitu deklarasi dan definisi. Ini hanya praktik yang baik untuk meletakkan deklarasi dalam file header dan definisi dalam file .c atau .cpp.
Di sisi lain, dimungkinkan untuk memiliki model kompilasi di mana kompiler tahu betul jika mengimpor deklarasi simbol global yang didefinisikan dalam modul lain , atau jika sedang menyusun definisi simbol global yang disediakan oleh modul saat ini . Hanya dalam kasus terakhir kompiler harus meletakkan simbol ini (misalnya variabel) di file objek saat ini.
Misalnya, dalam GNU Pascal Anda dapat menulis sebuah unit a
dalam file a.pas
seperti ini:
unit a;
interface
var MyStaticVariable: Integer;
implementation
begin
MyStaticVariable := 0
end.
di mana variabel global dideklarasikan dan diinisialisasi dalam file sumber yang sama.
Kemudian Anda dapat memiliki unit berbeda yang mengimpor dan menggunakan variabel global
MyStaticVariable
, misalnya unit b ( b.pas
):
unit b;
interface
uses a;
procedure PrintB;
implementation
procedure PrintB;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
dan unit c ( c.pas
):
unit c;
interface
uses a;
procedure PrintC;
implementation
procedure PrintC;
begin
Inc(MyStaticVariable);
WriteLn(MyStaticVariable)
end;
end.
Akhirnya Anda dapat menggunakan unit b dan c dalam program utama m.pas
:
program M;
uses b, c;
begin
PrintB;
PrintC;
PrintB
end.
Anda dapat mengkompilasi file-file ini secara terpisah:
$ gpc -c a.pas
$ gpc -c b.pas
$ gpc -c c.pas
$ gpc -c m.pas
dan kemudian menghasilkan executable dengan:
$ gpc -o m m.o a.o b.o c.o
dan jalankan:
$ ./m
1
2
3
Kuncinya di sini adalah bahwa ketika kompilator melihat arahan kegunaan dalam modul program (mis. Menggunakan a di b.pas), kompiler tidak menyertakan file .pas yang sesuai, tetapi mencari file .gpi, yaitu untuk pra-kompilasi file antarmuka (lihat dokumentasi ). .gpi
File - file ini dihasilkan oleh kompilator bersama dengan .o
file ketika setiap modul dikompilasi. Jadi simbol global MyStaticVariable
hanya didefinisikan sekali dalam file objek a.o
.
Java bekerja dengan cara yang sama: ketika kompiler mengimpor kelas A ke kelas B, ia melihat file kelas untuk A dan tidak memerlukan file A.java
. Jadi semua definisi dan inisialisasi untuk kelas A dapat dimasukkan ke dalam satu file sumber.
Kembali ke C ++, alasan mengapa dalam C ++ Anda harus mendefinisikan anggota data statis dalam file terpisah lebih terkait dengan model kompilasi C ++ daripada keterbatasan yang dikenakan oleh linker atau alat lain yang digunakan oleh kompiler. Di C ++, mengimpor beberapa simbol berarti membangun deklarasi mereka sebagai bagian dari unit kompilasi saat ini. Ini sangat penting, antara lain, karena cara templat dikompilasi. Tetapi ini menyiratkan bahwa Anda tidak dapat / tidak boleh mendefinisikan simbol global (fungsi, variabel, metode, anggota data statis) dalam file yang disertakan, jika tidak, simbol-simbol ini dapat didefinisikan berlipat ganda dalam file objek yang dikompilasi.