← Назад к вопросам
Что такое 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:
- Используйте debugger — установите точку останова на конструктор/деструктор
- Проверьте стек вызовов — увидите, откуда идёт вызов
- Добавьте логирование — в конструктор и деструктор
- Проверьте инициализацию членов — убедитесь, что всё инициализировано до вызова virtual функций
Выводы
- Pure virtual call — это результат вызова чистой виртуальной функции, которая не имеет реализации
- Главная причина — вызов virtual функций из конструктора/деструктора
- Решение — используйте NVI, двухэтапную инициализацию или CRTP
- Профилактика — никогда не вызывайте virtual функции из конструкторов и деструкторов