← Назад к вопросам

Что такое pure virtual call?

2.0 Middle🔥 71 комментариев
#ООП и проектирование#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Pure Virtual Call в C++

Pure Virtual Call — это ошибка, которая возникает, когда программа пытается вызвать чистую виртуальную функцию (pure virtual function) во время выполнения. Это одна из самых коварных и сложных для отладки ошибок в C++, так как может привести к неопределённому поведению (Undefined Behavior).

Что такое pure virtual function

Чистая виртуальная функция — это функция в базовом классе, которая не имеет реализации и ДОЛЖНА быть переопределена в любом производном классе.

class Shape {
public:
    virtual void draw() = 0;  // Pure virtual function
    virtual ~Shape() = default;
};

class Circle : public Shape {
public:
    void draw() override {
        std::cout << "Drawing circle" << std::endl;
    }
};

Когда возникает pure virtual call

1. Вызов из конструктора/деструктора базового класса

Это самая частая причина. Во время конструирования производного класса сначала выполняется конструктор базового класса. В этот момент объект ещё не полностью инициализирован.

class Base {
public:
    Base() {
        init();  // ОПАСНО!
    }
    
    virtual void init() = 0;
    virtual ~Base() = default;
};

class Derived : public Base {
private:
    int value = 0;
    
public:
    Derived() {
        value = 42;  // Ещё не инициализирована!
    }
    
    void init() override {
        std::cout << "Value: " << value << std::endl;  // Может быть мусор
    }
};

int main() {
    Derived d;  // ОШИБКА: pure virtual call!
    return 0;
}

Почему это проблема:

  • Во время выполнения конструктора Base::Base() переменная vtable всё ещё указывает на Base
  • Вызов init() попытается вызвать Base::init() (которая = 0)
  • Переменные в Derived ещё не инициализированы

2. Вызов из деструктора

class Base {
public:
    virtual ~Base() {
        cleanup();  // ОПАСНО!
    }
    
    virtual void cleanup() = 0;
};

class Derived : public Base {
private:
    std::vector<int> data;
    
public:
    void cleanup() override {
        data.clear();  // В деструкторе: data уже разрушена!
    }
};

int main() {
    Derived d;
    // Деструктор Derived вызывает ~Base
    // ~Base вызывает cleanup()
    // cleanup() пытается обратиться к уже разрушённому data
    return 0;
}

3. Косвенный вызов через другие virtual функции

class Base {
public:
    Base() {
        doWork();  // Вызывает virtual функцию
    }
    
    virtual void doWork() {
        process();  // Pure virtual в производном
    }
    
    virtual void process() = 0;
};

class Derived : public Base {
public:
    void process() override {
        std::cout << "Processing" << std::endl;
    }
};

int main() {
    Derived d;  // ОШИБКА: pure virtual call через doWork() -> process()
    return 0;
}

Как избежать pure virtual call

1. Не вызывайте virtual функции из конструктора/деструктора

// ПЛОХО
class BadExample {
public:
    BadExample() {
        virtual_method();  // Не делайте так!
    }
    
    virtual void virtual_method() = 0;
};

// ХОРОШО: используйте non-virtual interface (NVI) паттерн
class GoodExample {
public:
    GoodExample() {
        nonVirtualInit();  // Вызвать non-virtual функцию
    }
    
protected:
    void nonVirtualInit() {
        // Инициализация базового класса
        virtual_init();  // Можно вызвать virtual здесь
    }
    
    virtual void virtual_init() = 0;
};

2. Используйте двухэтапную инициализацию

class ProperClass {
private:
    bool initialized = false;
    
public:
    ProperClass() {
        // Конструктор только инициализирует базовое состояние
    }
    
    void initialize() {
        if (!initialized) {
            setup();
            initialized = true;
        }
    }
    
    virtual void setup() = 0;
    virtual ~ProperClass() = default;
};

class DerivedClass : public ProperClass {
public:
    void setup() override {
        std::cout << "Setting up" << std::endl;
    }
};

int main() {
    DerivedClass obj;
    obj.initialize();  // Явный вызов после полной инициализации
    return 0;
}

3. Используйте фабрики для создания объектов

class Base {
public:
    static std::unique_ptr<Base> create();
    virtual void initialize() = 0;
    virtual ~Base() = default;
protected:
    Base() = default;  // Защищённый конструктор
};

class Derived : public Base {
public:
    void initialize() override {
        std::cout << "Initialized" << std::endl;
    }
};

std::unique_ptr<Base> Base::create() {
    auto obj = std::make_unique<Derived>();
    obj->initialize();  // Инициализация после полного конструирования
    return obj;
}

int main() {
    auto obj = Base::create();  // Безопасно
    return 0;
}

4. CRTP (Curiously Recurring Template Pattern)

template<typename Derived>
class Base {
public:
    Base() {
        static_cast<Derived*>(this)->initialize();
    }
    
    virtual ~Base() = default;
};

class MyClass : public Base<MyClass> {
public:
    void initialize() {
        std::cout << "MyClass initialized" << std::endl;
    }
};

int main() {
    MyClass obj;  // Безопасно: нет virtual функций
    return 0;
}

Отладка pure virtual call

Eсли вы столкнулись с pure virtual call:

  1. Используйте debugger — установите точку останова на конструктор/деструктор
  2. Проверьте стек вызовов — увидите, откуда идёт вызов
  3. Добавьте логирование — в конструктор и деструктор
  4. Проверьте инициализацию членов — убедитесь, что всё инициализировано до вызова virtual функций

Выводы

  • Pure virtual call — это результат вызова чистой виртуальной функции, которая не имеет реализации
  • Главная причина — вызов virtual функций из конструктора/деструктора
  • Решение — используйте NVI, двухэтапную инициализацию или CRTP
  • Профилактика — никогда не вызывайте virtual функции из конструкторов и деструкторов
Что такое pure virtual call? | PrepBro