Я иногда замечаю программы, которые вылетают на моем компьютере с ошибкой: «вызов чистой виртуальной функции».
Как эти программы вообще компилируются, если объект не может быть создан из абстрактного класса?
c++
polymorphism
virtual-functions
pure-virtual
Брайан Р. Бонди
источник
источник
doIt()
вызов в конструкторе легко девиртуализируется и передаетсяBase::doIt()
статически, что вызывает ошибку компоновщика. Что нам действительно нужно, так это ситуация, в которой динамический тип во время динамической отправки является абстрактным базовым типом.Base::Base
вызовите не виртуальный,f()
который, в свою очередь, вызывает (чистый) виртуальныйdoIt
метод.Помимо стандартного случая вызова виртуальной функции из конструктора или деструктора объекта с чистыми виртуальными функциями, вы также можете получить вызов чистой виртуальной функции (по крайней мере, на MSVC), если вы вызываете виртуальную функцию после того, как объект был уничтожен. . Очевидно, это довольно плохая идея, но если вы работаете с абстрактными классами в качестве интерфейсов и ошибаетесь, то вы можете это увидеть. Вероятно, это более вероятно, если вы используете интерфейсы с подсчетом ссылок и у вас есть ошибка подсчета ссылок или если у вас есть состояние гонки использования / уничтожения объекта в многопоточной программе ... Суть этих видов чистых вызовов заключается в том, что они Часто бывает труднее понять, что происходит, так как проверка «обычных подозреваемых» виртуальных вызовов в ctor и dtor оказывается чистой.
Чтобы помочь с отладкой такого рода проблем, вы можете в различных версиях MSVC заменить обработчик purecall библиотеки времени выполнения. Вы делаете это, предоставляя свою собственную функцию с этой подписью:
и связать его перед тем, как связать библиотеку времени выполнения. Это дает ВАМ контроль над тем, что происходит при обнаружении чистого вызова. Получив контроль, вы можете делать что-то более полезное, чем стандартный обработчик. У меня есть обработчик, который может предоставить трассировку стека того, где произошел чистый вызов; посмотреть здесь: http://www.lenholgate.com/blog/2006/01/purecall.html для получения дополнительных сведений.
(Обратите внимание, что вы также можете вызвать _set_purecall_handler (), чтобы установить обработчик в некоторых версиях MSVC).
источник
_purecall()
вызов, который обычно происходит при вызове метода удаленного экземпляра, не произойдет, если базовый класс был объявлен с__declspec(novtable)
оптимизацией (специфично для Microsoft). При этом вполне возможно вызвать переопределенный виртуальный метод после удаления объекта, что может замаскировать проблему, пока она не укусит вас в какой-либо другой форме._purecall()
Ловушка является вашим другом!Обычно, когда вы вызываете виртуальную функцию через висящий указатель - скорее всего, экземпляр уже был уничтожен.
Могут быть и более «творческие» причины: возможно, вам удалось отрезать часть вашего объекта, где была реализована виртуальная функция. Но обычно просто экземпляр уже уничтожен.
источник
Я столкнулся со сценарием, когда чистые виртуальные функции вызываются из-за уничтоженных объектов,
Len Holgate
уже есть очень хороший ответ , я хотел бы добавить немного цвета с примером:Деструктор производного класса сбрасывает точки vptr на базовый класс vtable, который имеет чистую виртуальную функцию, поэтому, когда мы вызываем виртуальную функцию, она фактически вызывает чистые вирутальные функции.
Это могло произойти из-за очевидной ошибки кода или сложного сценария состояния гонки в многопоточных средах.
Вот простой пример (компиляция g ++ с отключенной оптимизацией - простую программу можно легко оптимизировать):
А трассировка стека выглядит так:
Выделите:
если объект полностью удален, то есть вызывается деструктор и восстанавливается memroy, мы можем просто получить,
Segmentation fault
поскольку память вернулась в операционную систему, и программа просто не может получить к ней доступ. Таким образом, этот сценарий «вызова чистой виртуальной функции» обычно происходит, когда объект выделяется в пуле памяти, в то время как объект удаляется, базовая память фактически не используется ОС, она все еще доступна для процесса.источник
Я предполагаю, что для абстрактного класса по какой-то внутренней причине создан vtbl (он может понадобиться для какой-то информации о типе времени выполнения), и что-то идет не так, и реальный объект получает это. Это ошибка. Уже одно это должно сказать, что то, чего не может случиться, есть.
Чистая спекуляция
edit: похоже, что я ошибаюсь в рассматриваемом случае. OTOH IIRC некоторые языки действительно разрешают вызовы vtbl из деструктора конструктора.
источник
Я использую VS2010, и всякий раз, когда я пытаюсь вызвать деструктор непосредственно из общедоступного метода, я получаю ошибку «вызов чистой виртуальной функции» во время выполнения.
Итак, я переместил то, что внутри ~ Foo (), в отдельный частный метод, и тогда он работал как шарм.
источник
Если вы используете Borland / CodeGear / Embarcadero / Idera C ++ Builder, вы можете просто реализовать
Во время отладки поместите точку останова в код и просмотрите стек вызовов в IDE, в противном случае зарегистрируйте стек вызовов в обработчике исключений (или этой функции), если у вас есть для этого соответствующие инструменты. Я лично использую для этого MadExcept.
PS. Исходный вызов функции находится в [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp
источник
Вот хитрый способ, чтобы это произошло. По сути, это случилось со мной сегодня.
источник
I had this essentially happen to me today
очевидно, неверно, потому что просто неправильно: чистая виртуальная функция вызывается только тогда, когдаcallFoo()
вызывается в конструкторе (или деструкторе), потому что в это время объект все еще (или уже) находится на стадии A. Вот работающая версия вашего кода без синтаксической ошибкиB b();
- круглые скобки означают объявление функции, вам нужен объект.