2008-09-19 13 views
90

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

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

ответ

92

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

(см видеодемонстрация here)

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 
} 
+2

Любые причины, по которым компилятор не мог поймать это, в общем? – Thomas 2008-09-19 04:15:10

0

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

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

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

+0

Это не ошибка в компиляторе, если это то, что вы имеете в виду. – Thomas 2008-09-19 04:20:26

+0

Ваши подозрения верны - C# и Java позволяют это. На этих языках строящиеся объекты действительно имеют свой конечный тип. В C++ объекты изменяют тип во время построения, и именно поэтому и когда вы можете иметь объекты с абстрактным типом. – MSalters 2008-09-19 10:42:44

+0

* ВСЕ * абстрактные классы и реальные объекты, созданные из них, нуждаются в vtbl (таблица виртуальных функций), перечисляя, какие виртуальные функции должны быть вызваны на него. В C++ объект отвечает за создание своих собственных членов, включая таблицу виртуальных функций. Конструкторы вызываются из базового класса в производный, а деструкторы вызываются из производного в базовый класс, поэтому в абстрактном базовом классе таблица виртуальных функций еще недоступна. – fuzzyTew 2009-07-17 11:00:43

7

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

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

-1

Вот проницательный путь для этого. Сегодня это случилось со мной.

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

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

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

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

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

0

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