Что может вызвать чисто виртуальный вызов функции в C++?
Я преподаю класс программирования C++, и я видел достаточно классов ошибок, которые у меня есть хорошее представление о том, как диагностировать распространенные ошибки C++. Однако есть один основной тип ошибок, для которого моя интуиция не особенно хороша: какие ошибки программирования вызывают вызовы чисто виртуальных функций? Самая распространенная ошибка, которую я видел, это вызвало вызов виртуальной функции из конструктора или деструктора базового класса. Есть ли еще что-то, о чем я должен знать, помогая отлаживать код студента?
4 ответа
"Самая распространенная ошибка, которую я видел, это вызвало вызов виртуальной функции из конструктора или деструктора базового класса".
Когда объект создается, указатель на виртуальную таблицу диспетчеризации изначально нацелен на самый высокий суперкласс, и он обновляется только после завершения построения промежуточных классов. Таким образом, вы можете случайно вызвать чисто виртуальную реализацию до того момента, пока подкласс - с его собственной реализацией переопределяющей функции - не завершит конструирование. Это может быть самый производный подкласс, или где-то между ними.
Это может произойти, если вы будете следовать указателю на частично сконструированный объект (например, в состоянии гонки из-за асинхронных или многопоточных операций).
Если у компилятора есть основания полагать, что он знает реальный тип, на который указывает указатель на базовый класс, он может разумно обойти виртуальную диспетчеризацию. Вы можете запутать это, делая что-то с неопределенным поведением, например, переосмыслением приведения.
Во время уничтожения виртуальная диспетчерская таблица должна быть возвращена, поскольку производные классы уничтожены, так что снова может быть вызвана чистая виртуальная реализация.
После уничтожения дальнейшее использование объекта с помощью "висящих" указателей или ссылок может вызывать чисто виртуальную функцию, но в таких ситуациях нет определенного поведения.
Вот несколько случаев, когда может происходить чисто виртуальный вызов.
- Использование висячего указателя - указатель не является допустимым объектом, поэтому виртуальная таблица, на которую он указывает, является просто случайной памятью, которая может содержать NULL
- Плохой актёрский состав
static_cast
неправильный тип (или приведение в стиле C) также может привести к тому, что объект, на который вы указываете, не будет иметь правильных методов в своей виртуальной таблице (в этом случае, по крайней мере, это действительно виртуальная таблица в отличие от предыдущего варианта). - DLL была выгружена - если объект, за который вы держитесь, был создан в общем объектном файле (DLL, итак, sl), который был выгружен снова, память теперь можно обнулить
В дополнение к другим ответам:
Это также может произойти, если производный класс объявлен без vtable.
Для Visual Studio, то есть если она определена, например, с помощью __declspec(novtable) или _ATL_NO_VTABLE. Это может случиться при работе с технологией MFC.
Если класс определен таким образом, виртуальная таблица будет заполнена _purecall.
Это может произойти, например, когда ссылка или указатель на объект указывает на пустое местоположение, и вы используете ссылку на объект или указатель для вызова виртуальной функции в классе. Например:
std::vector <DerivedClass> objContainer;
if (!objContainer.empty())
const BaseClass& objRef = objContainer.front();
// Do some processing using objRef and then you erase the first
// element of objContainer
objContainer.erase(objContainer.begin());
const std::string& name = objRef.name();
// -> (name() is a pure virtual function in base class,
// which has been implemented in DerivedClass).
На данный момент объект, хранящийся в objContainer[0], не существует. Когда виртуальная таблица проиндексирована, правильная ячейка памяти не найдена. Следовательно, во время выполнения выдается сообщение "чисто виртуальная функция вызвана".