Pure virtual function call

Вопрос по С++. Что за ошибка «pure virtual function call»? В какой ситуации она может быть сгенерирована? Предоставьте минимальный код, приводящий к ней.

Те, кто столкнулись с этой ошибкой в живом проекте и не знали про неё ранее, наверняка потратили немало времени на отлов этого бага. Разберём его по полочкам.

Ловили ошибку pure virtual function call хотя бы раз в своём проекте?

Загрузка ... Загрузка ...

Как работает механизм виртуальных функций? Обычно он реализуется через «vtbl» (virtual table) — таблицу с указателями на функции. Каждый экземпляр класса, содержащего хотя бы одну виртуальную функцию имеет указатель __vtbl на таблицу vtbl для своего класса. В случае с абстрактным классом и чистой виртуальной функцией, указатель всё равно есть, но на стандартный обработчик __pure_virtual_func_called(), который и приводит к такой ошибке. Но как его вызвать, ведь прямая попытка будет отловлена уже на этапе компиляции?

#include <iostream>

class Base
{
public:
    Base() { init(); }
    ~Base() {}

    virtual void log() = 0;

private:
    void init() { log(); }	
};

class Derived: public Base
{
public:
    Derived() {}
    ~Derived() {}

    virtual void log() { std::cout << "Derived created" << std::endl; }
};

int main(int argc, char* argv[])
{
    Derived d;
    return 0;
}

Разберём, что происходит при инстанцировании экземпляра объекта класса-потомка, который содержит vtbl.

Шаг 1. Сконструировать базовую часть верхнего уровня:
а) Установить указатель __vtbl на vtbl родительского класса;
б) Сконструировать переменные экземпляра базового класса;
в) Выполнить тело конструктора базового класса.

Шаг 2. Наследуемая часть(-и) (рекурсивно):
а) Поменять указатель __vtbl на vtbl класса-потомка;
б) Сконструировать переменные класса-потомка;
в) Выполнить тело конструктора класса-потомка.

Теперь взглянем на пример на картинке. Несложно догадаться, что когда будет создаваться объект класса Derived, то на шаге выполнения конструктора базового класса, он сам по себе будет еще считаться базовым классом и его vtbl будет от базового класса. Обычно компиляторы не детектируют такое заранее и ошибка ловится только в runtime.

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

Почитать подробнее про это можно на artima.com или в книжке Скотта Майерса «Effective C++», совет номер 9.