Ответ
Pure virtual call (вызов чистой виртуальной функции) — это ошибка времени выполнения в C++, которая возникает при попытке вызвать чистую виртуальную функцию через указатель или ссылку на базовый класс, когда для фактического объекта производного класса эта функция не была переопределена (или объект базового класса не существует). Это приводит к неопределённому поведению (Undefined Behavior, UB), чаще всего — к аварийному завершению программы.
Механизм: Во время выполнения вызов виртуальной функции осуществляется через таблицу виртуальных функций (vtable). Если функция объявлена как чистая виртуальная (= 0), её запись в vtable базового класса обычно содержит указатель на специальную функцию-заглушку (например, __cxa_pure_virtual), которая и вызывает аварийное завершение.
Типичные причины возникновения:
- Вызов виртуальной функции в конструкторе или деструкторе базового класса. Внутри конструктора базового класса объект производного класса ещё не сконструирован (или уже разрушен в деструкторе), поэтому механизм виртуальных функций работает некорректно — вызывается версия функции из базового класса, которая является чистой виртуальной.
- Удаление объекта через указатель на базовый класс, у которого нет виртуального деструктора. Это может привести к неполному разрушению объекта и последующему обращению к частично уничтоженной vtable.
- Обращение к объекту после его уничтожения (use-after-free). Указатель "висящий" (dangling pointer) может быть использован для вызова виртуальной функции.
- Неправильная инициализация указателя или ссылки, когда они указывают на несуществующий объект базового класса.
Пример опасного кода:
#include <iostream>
class AbstractBase {
public:
virtual void doWork() = 0; // Чистая виртуальная функция
virtual ~AbstractBase() = default;
AbstractBase() {
// КРИТИЧЕСКАЯ ОШИБКА: вызов чистой виртуальной функции в конструкторе!
// На момент выполнения конструктора AbstractBase объект производного
// класса ещё не существует. Компилятор может предупредить об этом.
doWork(); // Pure virtual call! Неопределённое поведение.
}
};
class ConcreteDerived : public AbstractBase {
public:
void doWork() override {
std::cout << "ConcreteDerived::doWork() called.n";
}
};
int main() {
// При попытке создания объекта произойдёт pure virtual call.
// Программа, скорее всего, аварийно завершится.
ConcreteDerived obj; // ОПАСНО!
return 0;
}
Как избежать:
- Никогда не вызывайте виртуальные функции (особенно чистые) из конструкторов и деструкторов базового класса. Если нужна общая инициализация, вынесите её в отдельный невиртуальный метод (например,
init()), который будет явно вызван после полного создания объекта. - Для полиморфных базовых классов всегда объявляйте виртуальный деструктор. Это гарантирует корректное разрушение объектов производных классов.
- Внимательно управляйте временем жизни объектов. Избегайте висячих указателей и ссылок.
- Используйте умные указатели (
std::unique_ptr,std::shared_ptr) для автоматического управления временем жизни.
Ответ 18+ 🔞
А, ну это классика, ёпта! Pure virtual call — это когда твоя программа, такая вся из себя объектно-ориентированная, внезапно решает, что ей доверия ебать ноль ко всему твоему коду, и просто накрывается медным тазом с криком "я так не договаривалась!".
Представь себе: у тебя есть базовый класс, типа как абстрактный "Транспорт". И в нём объявлена чисто виртуальная функция поехать() = 0. Это как договорённость: "чувак, кто от меня наследуется — обязан рассказать, КАК именно он едет: на колёсах, крыльях или реактивной тяге". И вроде всё ок, ты создаёшь класс Тачка и там переопределяешь этот метод.
Но! Волнение ебать начинается, если ты попробуешь вызвать эту самую поехать() из конструктора самого базового класса "Транспорт". Это жесть! В этот момент объект Тачка ещё толком не родился, он в процессе сборки. И система виртуальных вызовов, такая вся хитрая, смотрит в свою табличку (vtable) и видит там запись: "а функция-то поехать() — чисто виртуальная, ёб твою мать!". И вместо нормального кода она дергает какую-то заглушку, которая сразу кричит "Pure virtual call!" и отправляет всю программу в срака.
Вот смотри, сам от себя охуеешь от такого кода:
class AbstractBase {
public:
virtual void doWork() = 0; // Чистая виртуальная. Говорит "сделай работу", но КАК — не говорит.
AbstractBase() {
// А вот тут, в рот меня чих-пых, мы её ВЫЗЫВАЕМ!
// Объект-потомок ещё не готов, мы в самом начале конструктора AbstractBase.
// Кто будет работать? Никто! Пиздец.
doWork(); // Pure virtual call! Неопределённое поведение, программа — труп.
}
};
Терпения ноль ебать у компиляторов на этот счёт. Некоторые могут предупредить, но часто это просто выстрелит в ногу во время выполнения.
Как не облажаться? Правила простые, как палка:
- Никогда, блядь, не зови виртуальные методы из конструктора или деструктора базового класса. Это как будить человека, который только что заснул, вопросом "а как ты спишь?". Да похуй, как он спит, он ещё не спит! Если нужна общая настройка — сделай отдельный невиртуальный метод
init()и вызывай его потом, когда объект уже собран. - Если класс задуман для полиморфизма (через указатель на базовый класс удаляют объект), делай деструктор виртуальным. Иначе получишь неполное разрушение и те же грабли с поломанной vtable.
- Следи за временем жизни объектов. Не тыкайся указателем туда, где объект уже умер (use-after-free). Это всё равно что спросить у трупа, как дела.
- Юзай умные указатели (
std::unique_ptr). Они хоть автоматически приберут за тобой, меньше шансов накосячить с удалением.
Короче, pure virtual call — это удивление пиздец для новичка и позор для бывалого. Ловится просто: не лезь с виртуальными вызовами туда, где объект ещё не сформировался или уже развалился. Всё.