Czasami zauważam awarie programów na moim komputerze z błędem: „czyste wywołanie funkcji wirtualnej”.
Jak te programy nawet kompilują się, gdy nie można utworzyć obiektu z klasy abstrakcyjnej?
c++
polymorphism
virtual-functions
pure-virtual
Brian R. Bondy
źródło
źródło
doIt()
wywołanie w konstruktorze jest łatwo dewirtualizowane i wysyłaneBase::doIt()
statycznie, co po prostu powoduje błąd konsolidatora. To, czego naprawdę potrzebujemy, to sytuacja, w której typ dynamiczny podczas dynamicznej wysyłki jest abstrakcyjnym typem bazowym.Base::Base
wywołanie niewirtualnego,f()
co z kolei wywołuje (czystą)doIt
metodę wirtualną .Oprócz standardowego wywołania funkcji wirtualnej z konstruktora lub destruktora obiektu z czystymi funkcjami wirtualnymi można również uzyskać wywołanie czystej funkcji wirtualnej (przynajmniej na MSVC), jeśli wywołasz funkcję wirtualną po zniszczeniu obiektu . Oczywiście jest to bardzo zła rzecz, ale jeśli pracujesz z klasami abstrakcyjnymi jako interfejsami i coś zepsujesz, możesz to zobaczyć. Jest to prawdopodobnie bardziej prawdopodobne, jeśli używasz odwołań zliczanych interfejsów i masz błąd liczby referencji lub jeśli masz warunek wyścigu użycie obiektu / zniszczenie obiektu w programie wielowątkowym ... Rzecz w tego rodzaju czystym wywołaniu polega na tym, że często trudniej jest odgadnąć, co się dzieje, jako że sprawdzanie „zwykłych podejrzanych” połączeń wirtualnych w ctor i dtor zostanie wyczyszczone.
Aby pomóc w debugowaniu tego rodzaju problemów, możesz w różnych wersjach MSVC zastąpić procedurę obsługi purecall biblioteki wykonawczej. Robisz to, udostępniając własną funkcję z tym podpisem:
i łączenie go przed połączeniem biblioteki wykonawczej. Daje to Tobie kontrolę nad tym, co się stanie, gdy zostanie wykryte czyste połączenie. Gdy już uzyskasz kontrolę, możesz zrobić coś bardziej użytecznego niż standardowa obsługa. Mam procedurę obsługi, która może zapewnić ślad stosu, gdzie wydarzyło się czyste wywołanie; zobacz tutaj: http://www.lenholgate.com/blog/2006/01/purecall.html, aby uzyskać więcej informacji.
(Zauważ, że możesz również wywołać _set_purecall_handler (), aby zainstalować program obsługi w niektórych wersjach MSVC).
źródło
_purecall()
wywołanie, które normalnie występuje przy wywołaniu metody usuniętej instancji, nie nastąpi, jeśli klasa bazowa została zadeklarowana z__declspec(novtable)
optymalizacją (specyficzna dla firmy Microsoft). Dzięki temu jest całkowicie możliwe wywołanie zastąpionej metody wirtualnej po usunięciu obiektu, która może maskować problem, dopóki nie ugryzie Cię w innej formie._purecall()
Pułapka jest twoim przyjacielem!Zwykle gdy wywołujesz funkcję wirtualną za pomocą wiszącego wskaźnika - najprawdopodobniej instancja została już zniszczona.
Powody mogą być też bardziej „kreatywne”: może udało ci się odciąć część obiektu, w której zaimplementowano funkcję wirtualną. Ale zwykle po prostu instancja została już zniszczona.
źródło
Wpadłem na scenariusz, że czyste funkcje wirtualne są wywoływane z powodu zniszczonych obiektów,
Len Holgate
mam już bardzo ładną odpowiedź , chciałbym dodać trochę koloru z przykładem:Destruktor klasy pochodnej resetuje punkty vptr do klasy podstawowej vtable, która ma czystą funkcję wirtualną, więc kiedy wywołujemy funkcję wirtualną, w rzeczywistości wywołuje ona funkcje czysto wirutalne.
Może się to zdarzyć z powodu oczywistego błędu w kodzie lub skomplikowanego scenariusza wyścigu w środowiskach wielowątkowych.
Oto prosty przykład (kompilacja g ++ z wyłączoną optymalizacją - prosty program można łatwo zoptymalizować na zewnątrz):
A ślad stosu wygląda następująco:
Atrakcja:
jeśli obiekt zostanie całkowicie usunięty, co oznacza, że zostanie wywołany destruktor, a pamięć zostanie odzyskana, możemy po prostu otrzymać a,
Segmentation fault
gdy pamięć wróciła do systemu operacyjnego, a program po prostu nie może uzyskać do niej dostępu. Tak więc ten scenariusz „czystego wywołania funkcji wirtualnej” zwykle ma miejsce, gdy obiekt jest alokowany w puli pamięci, podczas gdy obiekt jest usuwany, pamięć bazowa nie jest w rzeczywistości odzyskiwana przez system operacyjny i nadal jest dostępna dla procesu.źródło
Domyślam się, że istnieje vtbl utworzony dla klasy abstrakcyjnej z jakiegoś wewnętrznego powodu (może być potrzebny do jakiegoś rodzaju informacji o typie działania) i coś idzie nie tak i prawdziwy obiekt to dostaje. To błąd. Już samo to powinno powiedzieć, że coś, co nie może się zdarzyć, jest.
Czysta spekulacja
edycja: wygląda na to, że się mylę w omawianej sprawie. OTOH IIRC w niektórych językach zezwalają na wywołania vtbl z destruktora konstruktora.
źródło
Używam VS2010 i za każdym razem, gdy próbuję wywołać destruktor bezpośrednio z metody publicznej, podczas działania pojawia się błąd „czystego wywołania funkcji wirtualnej”.
Więc przeniosłem to, co jest w środku ~ Foo (), aby oddzielić metodę prywatną, a potem zadziałało jak urok.
źródło
Jeśli używasz Borland / CodeGear / Embarcadero / Idera C ++ Builder, możesz po prostu zaimplementować
Podczas debugowania umieść punkt przerwania w kodzie i zobacz stos wywołań w IDE, w przeciwnym razie zarejestruj stos wywołań w programie obsługi wyjątków (lub tej funkcji), jeśli masz do tego odpowiednie narzędzia. Osobiście używam MadExcept do tego.
PS. Oryginalne wywołanie funkcji znajduje się w [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
źródło
Oto podstępny sposób, aby to się stało. Zasadniczo spotkałem się z tym dzisiaj.
źródło
I had this essentially happen to me today
oczywiście nieprawda, ponieważ po prostu błędna: czysta funkcja wirtualna jest wywoływana tylko wtedy, gdycallFoo()
jest wywoływana w konstruktorze (lub destruktorze), ponieważ w tym momencie obiekt jest nadal (lub już) na etapie A. Oto działająca wersja twojego kodu bez błędu składniowegoB b();
- nawiasy sprawiają, że jest to deklaracja funkcji, chcesz obiekt.