Я преподаю С++-класс программирования, и я видел достаточно классов ошибок, которые у меня есть хорошее чувство, как диагностировать распространенные ошибки С++. Однако существует одна серьезная ошибка, для которой моя интуиция не особенно хороша: какие ошибки программирования вызывают вызовы для чистых виртуальных функций? Наиболее распространенная ошибка, которую я видел, вызывает это вызов виртуальной функции из конструктора базового класса или деструктора. Есть ли другие, о которых я должен знать, когда помогает отлаживать код студента?
Что может вызвать вызов чистой виртуальной функции в С++?
Ответ 1
"Самая распространенная ошибка, которую я видел, которая вызывает это, вызывает виртуальную функцию от конструктора базового класса или деструктора".
Когда объект сконструирован, указатель на таблицу виртуальной диспетчеризации первоначально нацелен на наивысший суперкласс, и он обновляется только по завершении строительства промежуточных классов. Таким образом, вы можете случайно назвать чистую виртуальную реализацию до тех пор, пока не будет создан подкласс - с его собственной переопределяющей функцией. Это может быть наиболее производный подкласс или где-нибудь между ними.
Это может произойти, если вы следуете указателю на частично сконструированный объект (например, в состоянии гонки из-за асинхронных или поточных операций).
Если у компилятора есть основания полагать, что он знает реальный тип, к которому относятся точки указателя к базовому классу, он может разумно обойти виртуальную диспетчеризацию. Вы можете смутить его, выполнив что-то с поведением undefined как реинтерпрет.
Во время уничтожения виртуальная таблица рассылки должна быть возвращена, поскольку производные классы уничтожены, поэтому снова можно вызвать чистую виртуальную реализацию.
После уничтожения продолжение использования объекта с помощью "оборванных" указателей или ссылок может вызвать чистую виртуальную функцию, но в таких ситуациях не существует определенного поведения.
Ответ 2
Вот несколько случаев, когда может произойти чистый виртуальный вызов.
- Использование оборванного указателя - указатель не имеет действительного объекта, поэтому виртуальная таблица, на которую он указывает, представляет собой просто случайную память, которая может содержать NULL
- Плохой прилив с использованием
static_cast
к неправильному типу (или приведение в стиле C) также может привести к тому, что объект, на который вы указываете, не имеет правильных методов в своей виртуальной таблице (в этом случае по крайней мере, это действительно виртуальная таблица в отличие от предыдущей опции). - DLL была выгружена. Если объект, который вы держите, был создан в общем объектном файле (DLL, so, sl), который был снова выгружен, память может быть обнулена
Ответ 3
Это может произойти, например, когда ссылка или указатель на объект указывает на местоположение NULL, и вы используете ссылку на объект или указатель на вызов виртуальной функции в классе. Например:
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], не существует. Когда виртуальная таблица индексируется, не обнаружено действительного местоположения памяти. Следовательно, выдается ошибка времени выполнения, говорящая "чистая виртуальная функция".