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



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



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

646   7  

7 ответов:

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

(см. демо здесь)

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");
}

во время отладки поместите точку останова в код и просмотрите стек вызовов в 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();

Comments

    Ничего не найдено.