Apa itu vtable
?
Mungkin berguna untuk mengetahui apa yang dibicarakan pesan kesalahan sebelum mencoba memperbaikinya. Saya akan mulai pada tingkat tinggi, kemudian bekerja ke beberapa detail lebih lanjut. Dengan begitu orang dapat langsung beralih begitu mereka merasa nyaman dengan pemahaman mereka tentang tabel. ... dan ada banyak orang yang melompati sekarang :) Bagi mereka yang tinggal di sekitar:
Sebuah vtable pada dasarnya adalah implementasi paling umum dari polimorfisme di C ++ . Ketika vtables digunakan, setiap kelas polimorfik memiliki vtable di suatu tempat dalam program; Anda dapat menganggapnya sebagai anggota static
data (tersembunyi) dari kelas. Setiap objek dari kelas polimorfik dikaitkan dengan vtable untuk kelas yang paling diturunkan. Dengan memeriksa asosiasi ini, program dapat mengerjakan keajaiban polimorfiknya. Peringatan penting: vtable adalah detail implementasi. Ini tidak diamanatkan oleh standar C ++, meskipun kebanyakan kompiler C ++ (semua?) Menggunakan vtables untuk mengimplementasikan perilaku polimorfik. Rincian yang saya sajikan adalah pendekatan yang khas atau masuk akal. Kompiler diizinkan untuk menyimpang dari ini!
Setiap objek polimorfik memiliki pointer (tersembunyi) ke vtable untuk kelas objek yang paling diturunkan (mungkin beberapa pointer, dalam kasus yang lebih kompleks). Dengan melihat pointer, program dapat mengetahui jenis objek "asli" (kecuali selama konstruksi, tetapi mari kita lewati case khusus itu). Sebagai contoh, jika suatu objek bertipe A
tidak menunjuk ke tabel A
, maka objek tersebut sebenarnya merupakan sub-objek dari sesuatu yang berasal A
.
Nama "vtable" berasal dari " v irtual function table ". Ini adalah tabel yang menyimpan pointer ke fungsi (virtual). Compiler memilih konvensi untuk bagaimana tabel ditata; pendekatan sederhana adalah melalui fungsi virtual dalam urutan yang dinyatakan dalam definisi kelas. Ketika fungsi virtual dipanggil, program mengikuti pointer objek ke vtable, pergi ke entri yang terkait dengan fungsi yang diinginkan, kemudian menggunakan pointer fungsi tersimpan untuk menjalankan fungsi yang benar. Ada berbagai trik untuk membuat ini berhasil, tetapi saya tidak akan membahasnya di sini.
Di mana / kapan vtable
dihasilkan?
Sebuah vtable dihasilkan secara otomatis (kadang-kadang disebut "dipancarkan") oleh kompiler. Kompiler dapat memancarkan vtable di setiap unit terjemahan yang melihat definisi kelas polimorfik, tetapi itu biasanya tidak perlu berlebihan. Alternatif ( digunakan oleh gcc , dan mungkin oleh orang lain) adalah memilih satu unit terjemahan untuk menempatkan vtable, mirip dengan bagaimana Anda akan memilih file sumber tunggal untuk meletakkan anggota data statis kelas. Jika proses pemilihan ini gagal untuk memilih unit terjemahan apa pun, maka vtable menjadi referensi yang tidak ditentukan. Karenanya kesalahan, yang pesannya diakui tidak terlalu jelas.
Demikian pula, jika proses pemilihan memang memilih unit terjemahan, tetapi file objek itu tidak disediakan untuk linker, maka vtable menjadi referensi yang tidak ditentukan. Sayangnya, pesan kesalahan dapat menjadi kurang jelas dalam kasus ini daripada dalam kasus di mana proses seleksi gagal. (Terima kasih kepada para penjawab yang menyebutkan kemungkinan ini. Saya mungkin akan lupa kalau tidak.)
Proses pemilihan yang digunakan oleh gcc masuk akal jika kita mulai dengan tradisi mencurahkan file sumber (tunggal) untuk setiap kelas yang membutuhkan satu untuk implementasinya. Akan lebih baik untuk memancarkan vtable ketika mengkompilasi file sumber itu. Sebut saja itu tujuan kami. Namun, proses seleksi perlu bekerja bahkan jika tradisi ini tidak diikuti. Jadi alih-alih mencari implementasi seluruh kelas, mari kita mencari implementasi anggota kelas tertentu. Jika tradisi diikuti - dan jika anggota itu benar-benar dilaksanakan - maka ini mencapai tujuannya.
Anggota yang dipilih oleh gcc (dan berpotensi oleh kompiler lain) adalah fungsi virtual non-inline pertama yang bukan virtual murni. Jika Anda adalah bagian dari kerumunan yang mendeklarasikan konstruktor dan destruktor sebelum fungsi anggota lainnya, maka destruktor itu memiliki peluang bagus untuk dipilih. (Anda memang ingat membuat destructor virtual, kan?) Ada pengecualian; Saya berharap bahwa pengecualian yang paling umum adalah ketika definisi inline disediakan untuk destruktor dan ketika destruktor default diminta (menggunakan " = default
").
Cerdik mungkin memperhatikan bahwa kelas polimorfik diizinkan untuk memberikan definisi sebaris untuk semua fungsi virtualnya. Bukankah itu menyebabkan proses seleksi gagal? Itu dalam kompiler yang lebih tua. Saya telah membaca bahwa kompiler terbaru telah mengatasi situasi ini, tetapi saya tidak tahu nomor versi yang relevan. Saya bisa mencoba mencari ini, tetapi lebih mudah untuk kode di sekitarnya atau menunggu kompiler mengeluh.
Singkatnya, ada tiga penyebab utama kesalahan "referensi tidak ditentukan ke vtable":
- Fungsi anggota tidak memiliki definisi.
- File objek tidak sedang ditautkan.
- Semua fungsi virtual memiliki definisi sebaris.
Penyebab ini sendiri tidak cukup untuk menyebabkan kesalahan sendiri. Sebaliknya, ini adalah apa yang akan Anda atasi untuk menyelesaikan kesalahan. Jangan berharap bahwa dengan sengaja menciptakan salah satu dari situasi ini pasti akan menghasilkan kesalahan ini; ada persyaratan lain. Berharap bahwa menyelesaikan situasi ini akan menyelesaikan kesalahan ini.
(Oke, nomor 3 mungkin sudah cukup ketika pertanyaan ini diajukan.)
Bagaimana cara memperbaiki kesalahan?
Selamat datang kembali orang-orang yang melompat ke depan! :)
- Lihatlah definisi kelas Anda. Temukan fungsi virtual non-inline pertama yang bukan virtual murni (bukan "
= 0
") dan yang definisinya Anda berikan (bukan " = default
").
- Jika tidak ada fungsi seperti itu, coba modifikasi kelas Anda sehingga ada satu. (Kesalahan mungkin teratasi.)
- Lihat juga jawaban oleh Philip Thomas untuk peringatan.
- Temukan definisi untuk fungsi itu. Jika tidak ada, tambahkan! (Kesalahan mungkin teratasi.)
- Periksa perintah tautan Anda. Jika tidak menyebutkan file objek dengan definisi fungsi itu, perbaiki itu! (Kesalahan mungkin teratasi.)
- Ulangi langkah 2 dan 3 untuk setiap fungsi virtual, lalu untuk setiap fungsi non-virtual, hingga kesalahan teratasi. Jika Anda masih macet, ulangi untuk setiap anggota data statis.
Contoh
Detail tentang apa yang harus dilakukan dapat bervariasi, dan kadang-kadang bercabang menjadi pertanyaan yang terpisah (seperti Apa itu referensi yang tidak ditentukan / kesalahan simbol eksternal yang tidak terselesaikan dan bagaimana cara memperbaikinya? ). Saya akan, bagaimanapun, memberikan contoh apa yang harus dilakukan dalam kasus tertentu yang mungkin membingungkan programmer yang lebih baru.
Langkah 1 menyebutkan memodifikasi kelas Anda sehingga memiliki fungsi tipe tertentu. Jika deskripsi fungsi itu melampaui kepala Anda, Anda mungkin berada dalam situasi yang ingin saya bahas. Ingatlah bahwa ini adalah cara untuk mencapai tujuan; itu bukan satu-satunya cara, dan dengan mudah bisa ada cara yang lebih baik dalam situasi spesifik Anda. Mari kita panggil kelasmu A
. Apakah destruktor Anda dinyatakan (dalam definisi kelas Anda) sebagai salah satu
virtual ~A() = default;
atau
virtual ~A() {}
? Jika demikian, dua langkah akan mengubah destruktor Anda menjadi jenis fungsi yang kita inginkan. Pertama, ubah baris itu menjadi
virtual ~A();
Kedua, letakkan baris berikut dalam file sumber yang merupakan bagian dari proyek Anda (sebaiknya file dengan implementasi kelas, jika Anda memilikinya):
A::~A() {}
Itu membuat destruktor (virtual) Anda non-inline dan tidak dihasilkan oleh kompiler. (Jangan ragu untuk memodifikasi hal-hal agar lebih cocok dengan gaya pemformatan kode Anda, seperti menambahkan komentar header ke definisi fungsi.)