Saya terkadang melihat program yang mogok di komputer saya dengan kesalahan: "panggilan fungsi virtual murni".
Bagaimana program-program ini bahkan dapat dikompilasi ketika sebuah objek tidak dapat dibuat dari kelas abstrak?
c++
polymorphism
virtual-functions
pure-virtual
Brian R. Bondy
sumber
sumber
doIt()
Panggilan di konstruktor mudah didevirtualisasi dan dikirim keBase::doIt()
statis, yang hanya menyebabkan kesalahan linker. Yang benar-benar kita butuhkan adalah situasi di mana tipe dinamis selama pengiriman dinamis adalah tipe dasar abstrak.Base::Base
panggil non-virtualf()
yang pada gilirannya memanggildoIt
metode virtual (murni) .Selain kasus standar pemanggilan fungsi virtual dari konstruktor atau destruktor objek dengan fungsi virtual murni, Anda juga bisa mendapatkan panggilan fungsi virtual murni (setidaknya pada MSVC) jika Anda memanggil fungsi virtual setelah objek dihancurkan . Jelas ini adalah hal yang sangat buruk untuk dicoba dan dilakukan tetapi jika Anda bekerja dengan kelas abstrak sebagai antarmuka dan Anda mengacaukannya maka itu adalah sesuatu yang mungkin Anda lihat. Ini mungkin lebih mungkin jika Anda menggunakan antarmuka terhitung yang direferensikan dan Anda memiliki bug jumlah referensi atau jika Anda memiliki kondisi balapan penggunaan / penghancuran objek dalam program multi-utas ... Hal tentang purecall semacam ini adalah bahwa itu seringkali kurang mudah untuk memahami apa yang terjadi karena pemeriksaan 'tersangka biasa' dari panggilan virtual di ctor dan dtor akan tampil bersih.
Untuk membantu dengan debugging jenis masalah ini, Anda dapat, di berbagai versi MSVC, mengganti penangan purecall perpustakaan runtime. Anda melakukan ini dengan memberikan fungsi Anda sendiri dengan tanda tangan ini:
dan menautkannya sebelum Anda menautkan pustaka runtime. Ini memberi ANDA kendali atas apa yang terjadi ketika purecall terdeteksi. Setelah Anda memiliki kendali, Anda dapat melakukan sesuatu yang lebih berguna daripada penangan standar. Saya memiliki penangan yang dapat memberikan jejak tumpukan di mana purecall terjadi; lihat di sini: http://www.lenholgate.com/blog/2006/01/purecall.html untuk lebih jelasnya.
(Perhatikan, Anda juga dapat memanggil _set_purecall_handler () untuk menginstal penangan Anda di beberapa versi MSVC).
sumber
_purecall()
pemanggilan yang biasanya terjadi saat memanggil metode dari instance yang dihapus tidak akan terjadi jika kelas dasar telah dideklarasikan dengan__declspec(novtable)
pengoptimalan (khusus Microsoft). Dengan itu, sangat mungkin untuk memanggil metode virtual yang diganti setelah objek dihapus, yang dapat menutupi masalah hingga menggigit Anda dalam bentuk lain. The_purecall()
perangkap adalah teman Anda!Biasanya ketika Anda memanggil fungsi virtual melalui penunjuk yang menggantung - kemungkinan besar instance tersebut telah dimusnahkan.
Ada juga alasan yang lebih "kreatif": mungkin Anda telah berhasil memisahkan bagian dari objek tempat fungsi virtual diimplementasikan. Tetapi biasanya hanya saja instance tersebut telah dihancurkan.
sumber
Saya mengalami skenario bahwa fungsi virtual murni dipanggil karena objek yang hancur,
Len Holgate
sudah memiliki jawaban yang sangat bagus , saya ingin menambahkan beberapa warna dengan contoh:Penghancur kelas turunan mengatur ulang poin vptr ke kelas dasar vtable, yang memiliki fungsi virtual murni, jadi ketika kita memanggil fungsi virtual, itu benar-benar memanggil ke virutal murni.
Ini dapat terjadi karena bug kode yang jelas, atau skenario rumit kondisi balapan di lingkungan multi-threading.
Berikut adalah contoh sederhana (kompilasi g ++ dengan pengoptimalan dinonaktifkan - program sederhana dapat dengan mudah dioptimalkan):
Dan jejak tumpukan terlihat seperti:
Menyoroti:
jika objek dihapus sepenuhnya, yang berarti destruktor dipanggil, dan memroy diambil kembali, kita mungkin hanya mendapatkan a
Segmentation fault
karena memori telah kembali ke sistem operasi, dan program tidak dapat mengaksesnya. Jadi skenario "panggilan fungsi virtual murni" ini biasanya terjadi ketika objek dialokasikan pada kumpulan memori, sementara objek dihapus, memori yang mendasari sebenarnya tidak diambil kembali oleh OS, itu masih dapat diakses oleh proses.sumber
Saya kira ada vtbl yang dibuat untuk kelas abstrak karena beberapa alasan internal (mungkin diperlukan untuk semacam info jenis waktu proses) dan ada yang tidak beres dan objek nyata mendapatkannya. Itu bug. Itu saja harus mengatakan bahwa sesuatu yang tidak dapat terjadi adalah.
Spekulasi murni
edit: sepertinya saya salah dalam kasus yang dimaksud. OTOH IIRC beberapa bahasa mengizinkan panggilan vtbl keluar dari destruktor konstruktor.
sumber
Saya menggunakan VS2010 dan setiap kali saya mencoba memanggil destruktor langsung dari metode publik, saya mendapatkan kesalahan "panggilan fungsi virtual murni" selama runtime.
Jadi saya memindahkan apa yang ada di dalam ~ Foo () ke metode privat terpisah, lalu itu bekerja seperti pesona.
sumber
Jika Anda menggunakan Borland / CodeGear / Embarcadero / Idera C ++ Builder, Anda cukup mengimplementasikannya
Saat proses debug, tempatkan breakpoint dalam kode dan lihat callstack di IDE, jika tidak, catat tumpukan panggilan di penangan pengecualian Anda (atau fungsi tersebut) jika Anda memiliki alat yang sesuai untuk itu. Saya pribadi menggunakan MadExcept untuk itu.
PS. Panggilan fungsi asli ada di [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
sumber
Inilah cara licik untuk mewujudkannya. Saya mengalami ini pada dasarnya terjadi pada saya hari ini.
sumber
I had this essentially happen to me today
jelas tidak benar, karena salah: fungsi virtual murni dipanggil hanya ketikacallFoo()
dipanggil di dalam konstruktor (atau destruktor), karena saat ini objek masih (atau sudah) pada tahap A. Berikut adalah versi kode Anda yang sedang berjalan tanpa kesalahan sintaks diB b();
- tanda kurung menjadikannya deklarasi fungsi, Anda menginginkan objek.