2011-01-06 3 views
31

Я преподаю C++-класс программирования, и я видел достаточно классов ошибок, и у меня есть хорошее чувство, как диагностировать распространенные ошибки C++. Однако есть одна серьезная ошибка, для которой моя интуиция не особенно хороша: Какие ошибки программирования вызывают вызовы для чистых виртуальных функций? Самая распространенная ошибка, которую я видел, которая вызывает это вызов виртуальной функции из конструктора базового класса или деструктора. Есть ли другие, о которых я должен знать, когда помогает отлаживать код студента?Что может вызвать вызов чистой виртуальной функции в C++?

+0

другие, возможно, называют это от некоторых функций-членов базового класса, что еще может быть там? но тогда это не ошибка! : | – Nawaz

+0

Это действительно мой вопрос. :-) Не может быть другого способа инициировать чистый виртуальный вызов, и мой главный вопрос в том, есть ли он. – templatetypedef

ответ

27

«Самая распространенная ошибка, которую я видел, которая вызывает это, - это вызов виртуальной функции из конструктора базового класса или деструктора».

Когда объект сконструирован, указатель на таблицу виртуальной диспетчеризации первоначально нацелен на высший суперкласс, и он обновляется только по завершении строительства промежуточных классов. Таким образом, вы можете случайно назвать чистую виртуальную реализацию до тех пор, пока не будет создан подкласс - с его собственной переопределяющей функцией. Это может быть самый производный подкласс или где-то между ними.

Это может произойти, если вы следуете указателю на частично сконструированный объект (например, в состоянии гонки из-за асинхронных или поточных операций).

Если у компилятора есть основания полагать, что он знает реальный тип, на который указывают точки указателя на базовый класс, он может разумно обойти виртуальную отправку. Вы можете смутить его, совершив что-то с неопределенным поведением, как реинтерпрет.

Во время уничтожения виртуальная таблица рассылки должна быть возвращена, поскольку производные классы уничтожены, поэтому снова может быть вызвана чистая виртуальная реализация.

После уничтожения продолжение использования объекта посредством «оборванных» указателей или ссылок может вызвать чистую виртуальную функцию, но в таких ситуациях нет определенного поведения.

+0

Фактически, виртуальные вызовы отключены в конструкторах и деструкторах - [объяснено здесь] (https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-fromcctors) - поэтому вызовы не являются виртуальными и компоновщиками ошибка происходит. Любая не виртуальная отправка приведет к ошибке компоновщика. Но я соглашаюсь на частично построенные (или поврежденные) объекты, когда виртуальная таблица просто неверна. Однако эти ситуации требуют динамических трюков, которые не видны компилятору (потоки, упомянутые, reinterpret_cast и т. Д.). – uvsmtid

+0

@uvsmtid: «На самом деле» подразумевает, что вы исправляете какое-то ошибочное утверждение, которое я сделал, но я не упоминал конструкторов и деструкторов, только * construction * и * destroy *. Конструкция состоит из инициализации элемента базового класса и нестатического элемента данных до того, как будут обработаны список инициализации и тело конструктора; это почти то же самое в обратном во время разрушения. Отправка на чистые виртуальные функции может произойти, как показано на рисунке [здесь] (http://coliru.stacked-crooked.com/a/29b4fa463e39e277), без потоков, async, 'reinterpret_cast' и т. Д. –

+0

Отдельно, по ссылке, которую вы предоставляете или в противном случае - я не могу представить, как проявляются ошибки компоновщика. Не могли бы вы показать мне код? Возможно, сайт coliru, с которым я связан в комментарии выше, ideone.com или что вы предпочитаете .... Приветствия –

8

Вот несколько случаев, когда может произойти чистый виртуальный вызов.

  1. Использование оборванного указателя - указатель не действительный объект так виртуальная таблицы он указует только случайная память, которая может содержать NULL
  2. Bad литой с помощью static_cast к неправильному type (или C-style cast) также может привести к тому, что объект, на который вы указываете, не имеет правильных методов в своей виртуальной таблице (в этом случае, по крайней мере, это действительно - это виртуальный стол в отличие от предыдущего варианта).
  3. DLL разгружен - Если объект вы держите на была создана в общий объект файла (DLL, так, С.Л.), который был выгружен снова память может быть обнулена Теперь
+0

Разве это не три способа получить один? – GManNickG

+2

Обработчик purecall часто является специальной функцией, а не NULL. – ephemient

+0

@GMan, довольно да, возможно, он не заслуживает отдельного маркера, но легче диагностировать, чем общий случай – Motti

0

Это может произойти, например, когда ссылка или указатель на объект указывает на местоположение 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], не существует. Когда виртуальная таблица индексируется, не обнаружено действительного местоположения памяти. Следовательно, выдается ошибка времени выполнения, говорящая «чистая виртуальная функция».

+0

Да, я попытался с ошибкой «чистый виртуальный метод». Какую ошибку вы видите? – cppcoder

+0

Пример неполный, поэтому я еще не восстановил его. Но, как я полагаю, вы вызываете виртуальную функцию для указателя поворота, я попробую это в ближайшее время. BTW: это неопределенное поведение, и поэтому он может сбой по-другому. – Wolf

Смежные вопросы