Saya mencari definisi kapan saya diizinkan untuk melakukan penerusan deklarasi suatu kelas dalam file header kelas lain:
Apakah saya boleh melakukannya untuk kelas dasar, untuk kelas yang diadakan sebagai anggota, untuk kelas yang diteruskan ke fungsi anggota dengan referensi, dll.?
c++
forward-declaration
c++-faq
Igor Oks
sumber
sumber
Jawaban:
Tempatkan diri Anda pada posisi kompiler: ketika Anda meneruskan mendeklarasikan suatu tipe, yang diketahui kompiler adalah bahwa tipe ini ada; ia tidak tahu apa-apa tentang ukuran, anggota, atau metode. Inilah sebabnya mengapa ini disebut tipe tidak lengkap . Oleh karena itu, Anda tidak dapat menggunakan tipe untuk mendeklarasikan anggota, atau kelas dasar, karena kompiler perlu mengetahui tata letak tipe tersebut.
Dengan asumsi deklarasi maju berikut.
Inilah yang bisa dan tidak bisa Anda lakukan.
Apa yang dapat Anda lakukan dengan jenis yang tidak lengkap:
Nyatakan anggota sebagai penunjuk atau referensi ke tipe tidak lengkap:
Menyatakan fungsi atau metode yang menerima / mengembalikan tipe tidak lengkap:
Tetapkan fungsi atau metode yang menerima / mengembalikan pointer / referensi ke tipe tidak lengkap (tetapi tanpa menggunakan anggotanya):
Apa yang tidak dapat Anda lakukan dengan jenis yang tidak lengkap:
Gunakan itu sebagai kelas dasar
Gunakan itu untuk mendeklarasikan anggota:
Tentukan fungsi atau metode menggunakan tipe ini
Gunakan metode atau bidangnya, sebenarnya mencoba untuk mengubah variabel dengan tipe tidak lengkap
Ketika datang ke template, tidak ada aturan absolut: apakah Anda dapat menggunakan tipe yang tidak lengkap karena parameter template tergantung pada cara tipe tersebut digunakan dalam template.
Misalnya,
std::vector<T>
mengharuskan parameternya menjadi tipe lengkap, sementaraboost::container::vector<T>
tidak. Terkadang, tipe yang lengkap diperlukan hanya jika Anda menggunakan fungsi anggota tertentu; ini adalah kasus untukstd::unique_ptr<T>
, misalnya.Templat yang terdokumentasi dengan baik harus menunjukkan dalam dokumentasinya semua persyaratan parameternya, termasuk apakah mereka perlu tipe yang lengkap atau tidak.
sumber
Aturan utamanya adalah bahwa Anda hanya dapat meneruskan kelas yang tata letak memorinya (dan dengan demikian fungsi anggota dan anggota data) tidak perlu diketahui dalam file yang Anda forwardkan mendeklarasikannya.
Ini akan mengesampingkan kelas dasar dan apa pun kecuali kelas yang digunakan melalui referensi dan petunjuk.
sumber
Lakos membedakan antara penggunaan kelas
Saya belum pernah melihatnya diucapkan dengan lebih ringkas :)
sumber
Selain petunjuk dan referensi ke tipe tidak lengkap, Anda juga dapat mendeklarasikan prototipe fungsi yang menentukan parameter dan / atau mengembalikan nilai yang merupakan tipe tidak lengkap. Namun, Anda tidak dapat menentukan fungsi yang memiliki parameter atau tipe pengembalian yang tidak lengkap, kecuali itu adalah pointer atau referensi.
Contoh:
sumber
Sejauh ini tidak ada jawaban yang menjelaskan kapan orang bisa menggunakan deklarasi templat kelas yang diteruskan. Jadi begini saja.
Templat kelas dapat diteruskan dinyatakan sebagai:
Mengikuti struktur jawaban yang diterima ,
Inilah yang bisa dan tidak bisa Anda lakukan.
Apa yang dapat Anda lakukan dengan jenis yang tidak lengkap:
Menyatakan anggota untuk menjadi penunjuk atau referensi ke tipe tidak lengkap di templat kelas lain:
Nyatakan anggota sebagai penunjuk atau referensi ke salah satu instantiasinya yang tidak lengkap:
Nyatakan templat fungsi atau templat fungsi anggota yang menerima / mengembalikan tipe tidak lengkap:
Menyatakan fungsi atau fungsi anggota yang menerima / mengembalikan salah satu instantiasinya yang tidak lengkap:
Tentukan templat fungsi atau templat fungsi anggota yang menerima / mengembalikan pointer / referensi ke tipe tidak lengkap (tetapi tanpa menggunakan anggotanya):
Tetapkan fungsi atau metode yang menerima / mengembalikan pointer / referensi ke salah satu instantiasinya yang tidak lengkap (tetapi tanpa menggunakan anggotanya):
Gunakan itu sebagai kelas dasar dari kelas template lain
Gunakan untuk mendeklarasikan anggota templat kelas lain:
Tentukan templat fungsi atau metode menggunakan jenis ini
Apa yang tidak dapat Anda lakukan dengan jenis yang tidak lengkap:
Gunakan salah satu instantiasinya sebagai kelas dasar
Gunakan salah satu instantiasinya untuk mendeklarasikan anggota:
Tetapkan fungsi atau metode menggunakan salah satu instantiasinya
Gunakan metode atau bidang dari salah satu instantiasinya, pada kenyataannya mencoba meredereferensi variabel dengan tipe tidak lengkap
Buat instantiasi eksplisit dari templat kelas
sumber
X
danX<int>
persis sama, dan hanya sintaks yang menyatakan maju berbeda dalam cara substantif, dengan semua kecuali 1 baris jawaban Anda sama dengan hanya mengambil Luc dans/X/X<int>/g
? Apakah itu benar-benar dibutuhkan? Atau apakah saya melewatkan detail kecil yang berbeda? Itu mungkin, tetapi saya telah membandingkan beberapa kali secara visual dan tidak dapat melihat ...Dalam file di mana Anda hanya menggunakan Pointer atau Referensi ke suatu kelas. Dan tidak ada fungsi anggota / anggota yang harus dipikirkan jika Pointer / referensi tersebut.
dengan
class Foo;
// meneruskan deklarasiKami dapat mendeklarasikan data anggota tipe Foo * atau Foo &.
Kami dapat mendeklarasikan (tetapi tidak mendefinisikan) fungsi dengan argumen, dan / atau mengembalikan nilai, dari tipe Foo.
Kami dapat mendeklarasikan anggota data statis tipe Foo. Ini karena anggota data statis didefinisikan di luar definisi kelas.
sumber
Saya menulis ini sebagai jawaban terpisah daripada hanya komentar karena saya tidak setuju dengan jawaban Luc Touraille, bukan karena alasan legalitas tetapi untuk perangkat lunak yang kuat dan bahaya salah tafsir.
Secara khusus, saya memiliki masalah dengan kontrak tersirat tentang apa yang Anda harapkan agar diketahui oleh pengguna antarmuka Anda.
Jika Anda mengembalikan atau menerima jenis referensi, maka Anda hanya mengatakan mereka dapat melewati pointer atau referensi yang mereka mungkin hanya tahu melalui deklarasi maju.
Ketika Anda mengembalikan jenis yang tidak lengkap
X f2();
maka Anda mengatakan penelepon Anda harus memiliki spesifikasi tipe lengkap X. Mereka membutuhkannya untuk membuat LHS atau objek sementara di situs panggilan.Demikian pula, jika Anda menerima jenis yang tidak lengkap, pemanggil harus membuat objek yang merupakan parameter. Bahkan jika objek itu dikembalikan sebagai tipe lain yang tidak lengkap dari suatu fungsi, situs panggilan membutuhkan deklarasi penuh. yaitu:
Saya pikir ada prinsip penting bahwa header harus menyediakan informasi yang cukup untuk menggunakannya tanpa ketergantungan yang membutuhkan header lainnya. Itu berarti tajuk harus dapat dimasukkan dalam unit kompilasi tanpa menyebabkan kesalahan kompiler ketika Anda menggunakan fungsi yang dinyatakannya.
Kecuali
Jika ketergantungan eksternal ini perilaku yang diinginkan . Alih-alih menggunakan kompilasi bersyarat Anda bisa memiliki persyaratan yang terdokumentasi dengan baik bagi mereka untuk menyediakan header mereka sendiri yang menyatakan X. Ini adalah alternatif untuk menggunakan #ifdefs dan dapat menjadi cara yang berguna untuk memperkenalkan tiruan atau varian lainnya.
Perbedaan penting adalah beberapa teknik templat di mana Anda secara eksplisit TIDAK diharapkan untuk membuat instantiate, disebutkan hanya supaya seseorang tidak marah kepada saya.
sumber
I disagree with Luc Touraille's answer
Jadi, beri dia komentar, termasuk tautan ke posting blog jika Anda membutuhkannya. Ini tidak menjawab pertanyaan yang diajukan. Jika semua orang memikirkan pertanyaan tentang cara kerja X jawaban yang dibenarkan tidak setuju dengan X melakukan hal itu atau memperdebatkan batasan di mana kita harus membatasi kebebasan kita untuk menggunakan X - kita hampir tidak akan memiliki jawaban nyata.Aturan umum yang saya ikuti adalah untuk tidak menyertakan file header apa pun kecuali saya harus. Jadi, kecuali saya menyimpan objek kelas sebagai variabel anggota kelas saya, saya tidak akan memasukkannya, saya hanya akan menggunakan deklarasi maju.
sumber
Selama Anda tidak membutuhkan definisi (pikirkan petunjuk dan referensi), Anda bisa lolos dengan deklarasi maju. Inilah sebabnya mengapa sebagian besar Anda akan melihatnya di header sementara file implementasi biasanya akan menarik header untuk definisi yang tepat.
sumber
Anda biasanya ingin menggunakan deklarasi maju dalam file header kelas ketika Anda ingin menggunakan tipe lain (kelas) sebagai anggota kelas. Anda tidak dapat menggunakan metode kelas yang dinyatakan maju dalam file header karena C ++ belum mengetahui definisi kelas itu pada saat itu. Itu logika Anda harus pindah ke file .cpp, tetapi jika Anda menggunakan fungsi template Anda harus mengurangi mereka hanya bagian yang menggunakan template dan memindahkan fungsi itu ke header.
sumber
Ambillah bahwa deklarasi maju akan membuat kode Anda dikompilasi (obj dibuat). Namun menghubungkan (penciptaan exe) tidak akan berhasil kecuali definisi yang ditemukan.
sumber
class A; class B { A a; }; int main(){}
, dan beri tahu saya bagaimana hasilnya. Tentu saja itu tidak akan dikompilasi. Semua jawaban yang tepat di sini menjelaskan mengapa dan tepat, konteks terbatas di mana maju-deklarasi adalah valid. Anda malah menulis ini tentang sesuatu yang sama sekali berbeda.Saya hanya ingin menambahkan satu hal penting yang dapat Anda lakukan dengan kelas yang diteruskan tidak disebutkan dalam jawaban Luc Touraille.
Apa yang dapat Anda lakukan dengan jenis yang tidak lengkap:
Tetapkan fungsi atau metode yang menerima / mengembalikan pointer / referensi ke tipe tidak lengkap dan meneruskan pointer / referensi ke fungsi lain.
Modul dapat melewati objek kelas yang dinyatakan maju ke modul lain.
sumber
Seperti, Luc Touraille telah menjelaskan dengan sangat baik di mana harus digunakan dan tidak menggunakan pernyataan maju kelas.
Saya hanya akan menambah alasan mengapa kita perlu menggunakannya.
Kita harus menggunakan deklarasi Teruskan sedapat mungkin untuk menghindari injeksi ketergantungan yang tidak diinginkan.
Karena
#include
file header ditambahkan pada banyak file, oleh karena itu, jika kita menambahkan header ke file header lain, itu akan menambahkan injeksi ketergantungan yang tidak diinginkan di berbagai bagian kode sumber yang dapat dihindari dengan menambahkan#include
header ke.cpp
file di mana saja memungkinkan daripada menambahkan ke file header lain dan gunakan deklarasi forward class sedapat mungkin dalam.h
file header .sumber