class A {
static int foo () {} // ok
static int x; // <--- needed to be defined separately in .cpp file
};
Saya tidak melihat perlunya A::x
mendefinisikan secara terpisah dalam file .cpp (atau file yang sama untuk template). Mengapa tidak dapat A::x
dideklarasikan dan didefinisikan pada saat yang sama?
Apakah itu dilarang karena alasan historis?
Pertanyaan utama saya adalah, apakah ini akan memengaruhi fungsionalitas apa pun jika static
anggota data dideklarasikan / didefinisikan pada waktu yang sama (sama dengan Java )?
c++
data
language-features
grammar
static-access
iammilind
sumber
sumber
inline static int x[] = {1, 2, 3};
. Lihat en.cppreference.com/w/cpp/language/static#Static_data_membersJawaban:
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 filea.pas
seperti ini: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
):dan unit c (
c.pas
):Akhirnya Anda dapat menggunakan unit b dan c dalam program utama
m.pas
:Anda dapat mengkompilasi file-file ini secara terpisah:
dan kemudian menghasilkan executable dengan:
dan jalankan:
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 globalMyStaticVariable
hanya didefinisikan sekali dalam file objeka.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.
sumber
Karena anggota statis dibagikan di antara SEMUA instance kelas, mereka harus didefinisikan dalam satu dan hanya satu tempat. Sungguh, mereka variabel global dengan beberapa batasan akses.
Jika Anda mencoba mendefinisikannya di header, mereka akan didefinisikan di setiap modul yang menyertakan header itu, dan Anda akan mendapatkan kesalahan saat menautkan karena menemukan semua definisi duplikat.
Ya, ini setidaknya sebagian merupakan masalah historis yang berasal dari cfront; kompiler dapat ditulis yang akan membuat semacam "static_members_of_everything.cpp" yang tersembunyi dan tautan ke sana. Namun, itu akan merusak kompatibilitas, dan tidak akan ada manfaat nyata untuk melakukannya.
sumber
static
variabel dideklarasikan / didefinisikan di tempat yang sama (seperti Java) lalu apa yang bisa salah?static
anggotatemplate
? Mereka diizinkan di semua file header karena mereka harus terlihat. Saya tidak membantah jawaban ini, tetapi tidak cocok dengan pertanyaan saya juga.Alasan yang mungkin untuk ini adalah bahwa ini membuat bahasa C ++ dapat diimplementasikan dalam lingkungan di mana file objek dan model tautan tidak mendukung penggabungan beberapa definisi dari beberapa file objek.
Deklarasi kelas (disebut deklarasi karena alasan yang baik) ditarik ke dalam beberapa unit terjemahan. Jika deklarasi berisi definisi untuk variabel statis, maka Anda akan berakhir dengan beberapa definisi dalam beberapa unit terjemahan (Dan ingat, nama-nama ini memiliki tautan eksternal.)
Situasi itu mungkin, tetapi mengharuskan penghubung untuk menangani banyak definisi tanpa mengeluh.
(Dan perhatikan bahwa ini bertentangan dengan Aturan Satu Definisi, kecuali jika hal itu dapat dilakukan sesuai dengan jenis simbol atau bagian seperti apa itu ditempatkan.)
sumber
Ada perbedaan besar antara C ++ dan Java.
Java beroperasi pada mesin virtualnya sendiri yang menciptakan semuanya menjadi lingkungan run-time-nya sendiri. Jika suatu definisi terlihat lebih dari sekali, hanya akan bertindak pada objek yang sama dengan yang diketahui oleh lingkungan runtime.
Dalam C ++ tidak ada "pemilik pengetahuan pamungkas": C ++, C, Fortran Pascal dll. Semuanya adalah "penerjemah" dari kode sumber (file CPP) ke dalam format perantara (file OBJ, atau file ".o", tergantung pada OS) di mana pernyataan diterjemahkan ke dalam instruksi mesin dan nama menjadi alamat tidak langsung yang dimediasi oleh tabel simbol.
Suatu program tidak dibuat oleh kompiler, tetapi oleh program lain ("penghubung"), yang menggabungkan semua OBJ-s bersama-sama (tidak peduli bahasa asalnya) dengan menunjuk kembali semua alamat yang mengarah ke simbol, ke arah mereka. definisi yang efektif.
By the way linker bekerja, definisi (apa yang menciptakan ruang fisik untuk suatu variabel) harus unik.
Perhatikan bahwa C ++ tidak dengan sendirinya tautan, dan bahwa penghubung tidak dikeluarkan oleh spesifikasi C ++: penghubung ada karena cara modul OS dibangun (biasanya dalam C dan ASM). C ++ harus menggunakannya sebagaimana mestinya.
Sekarang: file header adalah sesuatu yang harus "disisipkan ke" beberapa file CPP. Setiap file CPP diterjemahkan secara independen dari yang lain. Kompiler yang menerjemahkan file CPP yang berbeda, semua yang menerima-dalam definisi yang sama akan menempatkan " kode pembuatan " untuk objek yang ditentukan di semua OBJ yang dihasilkan.
Kompiler tidak tahu (dan tidak akan pernah tahu) apakah semua OBJ tersebut akan digunakan bersama untuk membentuk satu program atau secara terpisah untuk membentuk program independen yang berbeda.
Tautan tidak tahu bagaimana dan mengapa definisi ada dan dari mana asalnya (bahkan tidak tahu tentang C ++: setiap "bahasa statis" dapat menghasilkan definisi dan referensi untuk dihubungkan). Ia hanya tahu ada referensi ke "simbol" yang diberikan yang "didefinisikan" pada alamat yang diberikan.
Jika ada beberapa definisi (jangan bingung definisi dengan referensi) untuk simbol yang diberikan, linker tidak memiliki pengetahuan (menjadi bahasa agnostik) tentang apa yang harus dilakukan dengan mereka.
Ini seperti menggabungkan sejumlah kota untuk membentuk kota besar: jika Anda ditemukan memiliki dua " Time Square " dan sejumlah orang yang datang dari luar meminta untuk pergi ke " Time square ", Anda tidak dapat memutuskan berdasarkan teknis murni (tanpa pengetahuan tentang politik yang menetapkan nama-nama itu dan akan bertanggung jawab untuk mengelolanya) di tempat yang tepat untuk mengirimkannya.
sumber
Ini diperlukan karena jika tidak kompiler tidak tahu di mana harus meletakkan variabel. Setiap file cpp dikompilasi secara individual dan tidak tahu tentang yang lain. Tautan menyelesaikan variabel, fungsi, dll. Saya pribadi tidak melihat perbedaan antara anggota vtable dan statis (kita tidak harus memilih file apa yang didefinisikan oleh vtable).
Saya sebagian besar menganggap lebih mudah bagi penulis kompiler untuk mengimplementasikannya seperti itu. Vars statis di luar kelas / struct ada dan mungkin baik untuk alasan konsistensi atau karena akan lebih mudah untuk diterapkan bagi penulis kompiler mereka mendefinisikan pembatasan dalam standar.
sumber
Saya rasa saya menemukan alasannya. Mendefinisikan
static
variabel dalam ruang terpisah memungkinkan untuk menginisialisasi nilai apa pun. Jika tidak diinisialisasi maka akan default ke 0.Sebelum C ++ 11 inisialisasi kelas tidak diizinkan di C ++. Jadi seseorang tidak bisa menulis seperti:
Jadi sekarang untuk menginisialisasi variabel kita harus menulisnya di luar kelas sebagai:
Sebagaimana dibahas dalam jawaban lain juga,
int X::i
sekarang menjadi global dan menyatakan global dalam banyak file menyebabkan kesalahan tautan simbol banyak.Jadi kita harus mendeklarasikan
static
variabel kelas di dalam unit terjemahan yang terpisah. Namun, masih dapat diperdebatkan bahwa cara berikut harus menginstruksikan kompiler untuk tidak membuat banyak simbolsumber
A :: x hanyalah variabel global tetapi namespace'd ke A, dan dengan pembatasan akses.
Seseorang masih harus mendeklarasikannya, seperti variabel global lainnya, dan itu bahkan dapat dilakukan dalam proyek yang terhubung secara statis dengan proyek yang berisi sisa kode A.
Saya akan menyebut ini semua desain yang buruk, tetapi ada beberapa fitur yang dapat Anda manfaatkan dengan cara ini:
pesanan panggilan konstruktor ... Tidak penting untuk int, tetapi untuk anggota yang lebih kompleks yang mungkin mengakses variabel statis atau global lainnya, ini bisa menjadi sangat penting.
initializer statis - Anda dapat membiarkan klien memutuskan apa yang harus diinisialisasi oleh A :: x.
di c ++ dan c, karena Anda memiliki akses penuh ke memori melalui pointer, lokasi fisik variabel signifikan. Ada hal-hal yang sangat nakal yang dapat Anda manfaatkan berdasarkan di mana variabel berada di objek tautan.
Saya ragu ini "mengapa" situasi ini telah muncul. Ini mungkin hanya evolusi C berubah menjadi C ++, dan masalah kompatibilitas mundur yang menghentikan Anda dari mengubah bahasa sekarang.
sumber