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?
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?
Jawaban:
Mereka dapat terjadi jika Anda mencoba membuat panggilan fungsi virtual dari konstruktor atau destruktor. Karena Anda tidak dapat membuat panggilan fungsi virtual dari konstruktor atau destruktor (objek kelas turunan belum dibuat atau telah dihancurkan), ia memanggil versi kelas dasar, yang dalam kasus fungsi virtual murni, tidak tidak ada.
(Lihat demo langsung di sini )
class Base
{
public:
Base() { doIt(); } // DON'T DO THIS
virtual void doIt() = 0;
};
void Base::doIt()
{
std::cout<<"Is it fine to call pure virtual function from constructor?";
}
class Derived : public Base
{
void doIt() {}
};
int main(void)
{
Derived d; // This will cause "pure virtual function call" error
}
doIt()
Panggilan di konstruktor mudah didevirtualisasi dan dikirim ke Base::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-virtual f()
yang pada gilirannya memanggil doIt
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:
int __cdecl _purecall(void)
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).
_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.
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):
#include <iostream>
using namespace std;
char pool[256];
struct Base
{
virtual void foo() = 0;
virtual ~Base(){};
};
struct Derived: public Base
{
virtual void foo() override { cout <<"Derived::foo()" << endl;}
};
int main()
{
auto* pd = new (pool) Derived();
Base* pb = pd;
pd->~Derived();
pb->foo();
}
Dan jejak tumpukan terlihat seperti:
#0 0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1 0x00007ffff749b02a in __GI_abort () at abort.c:89
#2 0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x0000000000400f82 in main () at purev.C:22
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.
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.
Saya menggunakan VS2010 dan setiap kali saya mencoba memanggil destruktor langsung dari metode publik, saya mendapatkan kesalahan "panggilan fungsi virtual murni" selama runtime.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void SomeMethod1() { this->~Foo(); }; /* ERROR */
};
Jadi saya memindahkan apa yang ada di dalam ~ Foo () ke metode privat terpisah, lalu itu bekerja seperti pesona.
template <typename T>
class Foo {
public:
Foo<T>() {};
~Foo<T>() {};
public:
void _MethodThatDestructs() {};
void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
Jika Anda menggunakan Borland / CodeGear / Embarcadero / Idera C ++ Builder, Anda cukup mengimplementasikannya
extern "C" void _RTLENTRY _pure_error_()
{
//_ErrorExit("Pure virtual function called");
throw Exception("Pure virtual function called");
}
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
Inilah cara licik untuk mewujudkannya. Saya mengalami ini pada dasarnya terjadi pada saya hari ini.
class A
{
A *pThis;
public:
A()
: pThis(this)
{
}
void callFoo()
{
pThis->foo(); // call through the pThis ptr which was initialized in the constructor
}
virtual void foo() = 0;
};
class B : public A
{
public:
virtual void foo()
{
}
};
B b();
b.callFoo();
I had this essentially happen to me today
jelas tidak benar, karena salah: fungsi virtual murni dipanggil hanya ketika callFoo()
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 di B b();
- tanda kurung menjadikannya deklarasi fungsi, Anda menginginkan objek.