Откуда происходят сбои "pure virtual function call"?

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

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

7 ответов


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

(см. live demo здесь)

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
}

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

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

int __cdecl _purecall(void)

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

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


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

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


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

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

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


Я использую 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 */
};

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

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

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

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


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

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();