Откуда происходят "чисто виртуальные вызовы функций"? - PullRequest
101 голосов
/ 19 сентября 2008

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

Как эти программы компилируются, даже если объект не может быть создан из абстрактного класса?

Ответы [ 8 ]

101 голосов
/ 19 сентября 2008

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

(см. Демо-версию здесь )

class Base
{
public:
    Base() { doIt(); }  // DON'T DO THIS
    virtual void doIt() = 0;
};

void Base::doIt()
{
    std::cout<<"Is it fine to call pure virtual function from constructor?";
}

class Derived : public Base
{
    void doIt() {}
};

int main(void)
{
    Derived d;  // This will cause "pure virtual function call" error
}
62 голосов
/ 19 сентября 2008

Помимо стандартного случая вызова виртуальной функции из конструктора или деструктора объекта с чисто виртуальными функциями, вы также можете получить вызов чисто виртуальной функции (по крайней мере, в MSVC), если вы вызываете виртуальную функцию после объекта был разрушен. Очевидно, что это довольно неудачная попытка, но если вы работаете с абстрактными классами в качестве интерфейсов, и вы все перепутали, то это то, что вы можете увидеть. Вероятно, это более вероятно, если вы используете подсчитанные интерфейсы, на которые ссылаются, и у вас есть ошибка подсчета ссылок или если у вас есть условие гонки объекта / уничтожения объекта в многопоточной программе ... Суть этих видов чистого вызова в том, что это часто не так просто понять, что происходит, поскольку проверка «обычных подозреваемых» виртуальных вызовов в ctor и dtor будет чистой.

Чтобы помочь с отладкой такого рода проблем, вы можете в различных версиях MSVC заменить обработчик purecall библиотеки времени выполнения. Вы делаете это, предоставляя свою собственную функцию с этой подписью:

int __cdecl _purecall(void)

и связывание его перед связыванием библиотеки времени выполнения. Это дает вам контроль над тем, что происходит при обнаружении чистого вызова. Получив контроль, вы можете сделать что-то более полезное, чем стандартный обработчик. У меня есть обработчик, который может обеспечить трассировку стека того, где произошел чистый вызов; см. здесь: http://www.lenholgate.com/blog/2006/01/purecall.html для более подробной информации.

(Обратите внимание, что вы также можете вызвать _set_purecall_handler () для установки вашего обработчика в некоторых версиях MSVC).

7 голосов
/ 19 сентября 2008

Обычно, когда вы вызываете виртуальную функцию через свисающий указатель - скорее всего, экземпляр уже уничтожен.

Могут быть и более «творческие» причины: возможно, вам удалось отрезать часть вашего объекта, где была реализована виртуальная функция. Но обычно это просто, что экземпляр уже был уничтожен.

0 голосов
/ 09 мая 2019

Я столкнулся с тем, что чисто виртуальные функции вызываются из-за разрушенных объектов, Len Holgate уже есть очень хороший ответ, я хотел бы добавить немного цвета с примером:

  1. Создается производный объект, и указатель (как базовый класс) где-то сохранено
  2. Производный объект удален, но каким-то образом указатель все еще упоминается
  3. Указатель, который указывает на удаленное производное объект называется

Деструктор производного класса сбрасывает точки vptr в базовый класс vtable, который имеет чисто виртуальную функцию, поэтому, когда мы вызываем виртуальную функцию, она фактически вызывает чисто виртуальные.

Это может произойти из-за явной ошибки кода или сложного сценария состояния гонки в многопоточных средах.

Вот простой пример (компиляция g ++ с отключенной оптимизацией - простая программа может быть легко оптимизирована):

 #include <iostream>
 using namespace std;

 char pool[256];

 struct Base
 {
     virtual void foo() = 0;
     virtual ~Base(){};
 };

 struct Derived: public Base
 {
     virtual void foo() override { cout <<"Derived::foo()" << endl;}
 };

 int main()
 {
     auto* pd = new (pool) Derived();
     Base* pb = pd;
     pd->~Derived();
     pb->foo();
 }

А трассировка стека выглядит так:

#0  0x00007ffff7499428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007ffff749b02a in __GI_abort () at abort.c:89
#2  0x00007ffff7ad78f7 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#3  0x00007ffff7adda46 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#4  0x00007ffff7adda81 in std::terminate() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x00007ffff7ade84f in __cxa_pure_virtual () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#6  0x0000000000400f82 in main () at purev.C:22

Выделите:

если объект полностью удален, то есть вызывается деструктор и вызывается восстановление memroy, мы можем просто получить Segmentation fault, поскольку память вернулась в операционную систему, и программа просто не может получить к ней доступ. Таким образом, этот сценарий «чисто виртуального вызова функции» обычно происходит, когда объект размещается в пуле памяти, в то время как объект удаляется, базовая память фактически не восстанавливается ОС, она все еще там доступна для процесса.

0 голосов
/ 22 марта 2018

Если вы используете Borland / CodeGear / Embarcadero / Idera C ++ Builder, вы можете просто реализовать

extern "C" void _RTLENTRY _pure_error_()
{
    //_ErrorExit("Pure virtual function called");
    throw Exception("Pure virtual function called");
}

Во время отладки поместите точку останова в коде и просмотрите стек вызовов в IDE, в противном случае зарегистрируйте стек вызовов в обработчике исключений (или той функции), если у вас есть соответствующие инструменты для этого. Я лично использую MadExcept для этого.

PS. Исходный вызов функции находится в [C ++ Builder] \ source \ cpprtl \ Source \ misc \ pureerr.cpp

0 голосов
/ 16 ноября 2012

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

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void SomeMethod1() { this->~Foo(); }; /* ERROR */
};

Так что я переместил то, что внутри ~ Foo (), чтобы отделить приватный метод, затем это сработало как шарм.

template <typename T>
class Foo {
public:
  Foo<T>() {};
  ~Foo<T>() {};

public:
  void _MethodThatDestructs() {};
  void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */
};
0 голосов
/ 19 сентября 2008

Вот хитрый способ, чтобы это произошло. По сути, сегодня это случилось со мной.

class A
{
  A *pThis;
  public:
  A()
   : pThis(this)
  {
  }

  void callFoo()
  {
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor
  }

  virtual void foo() = 0;
};

class B : public A
{
public:
  virtual void foo()
  {
  }
};

B b();
b.callFoo();
0 голосов
/ 19 сентября 2008

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

Чистая спекуляция

edit: похоже, что я ошибаюсь в данном случае. OTOH IIRC некоторые языки допускают вызовы vtbl из деструктора конструктора.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...