it-swarm.com.ru

Откуда происходят "чисто виртуальные вызовы функций"?

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

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

101
Brian R. Bondy

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

(Смотрите живое демо здесь )

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
}
101
Adam Rosenfield

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

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

int __cdecl _purecall(void)

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

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

62
Len Holgate

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

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

7
Braden

Я использую 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
David Lee

Я столкнулся со сценарием, когда чисто виртуальные функции вызываются из-за разрушенных объектов, у 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 ([email protected]=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
Baiyan Huang

Если вы используете 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
Niki

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

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

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

0
BCS