← Назад к вопросам
Найти проблемы в коде (dynamic_cast)
1.0 Junior🔥 131 комментариев
#Исключения и обработка ошибок#ООП и проектирование#Язык C++
Условие
Проанализируйте следующий код и найдите все проблемы. Объясните, что произойдёт при выполнении и как исправить код.
#include <iostream>
class Base {
public:
void print() { std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void print() { std::cout << "Derived" << std::endl; }
void derivedOnly() { std::cout << "Derived only" << std::endl; }
};
void process(Base* ptr) {
Derived* d = dynamic_cast<Derived*>(ptr);
d->derivedOnly();
}
int main() {
Base* b1 = new Derived();
Base* b2 = new Base();
process(b1);
process(b2);
delete b1;
delete b2;
return 0;
}
Вопросы
- Скомпилируется ли этот код? Если нет, какая ошибка?
- Если скомпилируется, что произойдёт при выполнении?
- Какие проблемы есть в коде?
- Как исправить код, чтобы он работал корректно?
Подсказки
- Обратите внимание на требования к dynamic_cast
- Проверьте результат dynamic_cast перед использованием
- Подумайте о виртуальных функциях и деструкторах
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Решение: Анализ проблем с dynamic_cast
Проблема 1: dynamic_cast требует виртуального деструктора
Проблема: Базовый класс не имеет виртуального деструктора:
class Base {
public:
void print() { ... } // ❌ деструктор не виртуальный
};
Что произойдёт:
- Компиляция: Код компилируется, но с undefined behavior при выполнении
- Выполнение: dynamic_cast может не работать корректно, так как RTTI (Run-Time Type Information) недоступна без виртуального деструктора
- Утечка памяти: Когда вызовем
delete b1через Base*, разрушится только Base (Derived деструктор не вызовется)
Вывод кода БЕЗ исправлений:
Derived only
Segmentation Fault / Undefined Behavior
Проблема 2: Отсутствие проверки результата dynamic_cast
Проблема: После dynamic_cast не проверяется, вернул ли он nullptr:
void process(Base* ptr) {
Derived* d = dynamic_cast<Derived*>(ptr);
d->derivedOnly(); // ❌ если d == nullptr, это UB!
}
Что произойдёт при вызове process(b2):
- b2 указывает на Base (не на Derived)
- dynamic_cast вернёт nullptr (касты неверны)
- d == nullptr
- d->derivedOnly() вызовет undefined behavior (разыменование nullptr)
- Программа упадёт с Segmentation Fault
Проблема 3: Отсутствие виртуальной функции print
Проблема: Функция print не виртуальная:
class Base {
void print() { ... } // ❌ не virtual
};
class Derived : public Base {
void print() { ... } // это НЕ переопределение, это новая функция!
};
Что произойдёт:
Base* b = new Derived();
b->print(); // вызовет Base::print(), не Derived::print()
Исправленный код
#include <iostream>
class Base {
public:
// Виртуальный деструктор — ОБЯЗАТЕЛЕН для полиморфных типов!
virtual ~Base() = default;
// Виртуальная функция
virtual void print() {
std::cout << "Base" << std::endl;
}
};
class Derived : public Base {
public:
// Переопределяем виртуальную функцию (override уточняет intent)
void print() override {
std::cout << "Derived" << std::endl;
}
void derivedOnly() {
std::cout << "Derived only" << std::endl;
}
};
void process(Base* ptr) {
if (!ptr) return; // Проверяем на nullptr
// Правильный способ: проверяем результат dynamic_cast
Derived* d = dynamic_cast<Derived*>(ptr);
if (d != nullptr) {
d->derivedOnly();
} else {
std::cout << "Not a Derived instance" << std::endl;
}
}
int main() {
Base* b1 = new Derived();
Base* b2 = new Base();
process(b1); // Выведет: Derived only
process(b2); // Выведет: Not a Derived instance
// Теперь виртуальный деструктор вызовется правильно
delete b1; // вызовет Derived::~Derived() → Base::~Base()
delete b2; // вызовет Base::~Base()
return 0;
}
Вывод исправленного кода:
Derived only
Not a Derived instance
Лучший паттерн: Использование reference вместо указателя
void process(Base& ref) {
try {
Derived& d = dynamic_cast<Derived&>(ref);
d.derivedOnly();
} catch (const std::bad_cast& e) {
std::cout << "Not a Derived instance: " << e.what() << std::endl;
}
}
int main() {
Derived d;
Base b;
process(d); // Успешно
process(b); // Выбросит исключение bad_cast
return 0;
}
Преимущества:
- С указателями: nullptr нужно проверять вручную
- С ссылками: исключение выбросится автоматически (RAII)
- Экспресс отказ без проверки
if (ptr != nullptr)
Ключевые принципы
1. Виртуальный деструктор
// ✅ ПРАВИЛЬНО
class Base {
public:
virtual ~Base() = default;
};
// ❌ НЕПРАВИЛЬНО
class Base {
public:
~Base() {} // не виртуальный — утечка памяти!
};
2. Виртуальные функции для полиморфизма
// ✅ ПРАВИЛЬНО
class Base {
public:
virtual void print() { ... }
};
class Derived : public Base {
public:
void print() override { ... } // override — для проверки!
};
3. Проверка результата dynamic_cast
// ✅ ПРАВИЛЬНО (указатель)
Derived* d = dynamic_cast<Derived*>(ptr);
if (d != nullptr) {
d->method();
}
// ✅ ПРАВИЛЬНО (ссылка)
try {
Derived& d = dynamic_cast<Derived&>(ref);
d.method();
} catch (const std::bad_cast&) {
// обработка ошибки
}
// ❌ НЕПРАВИЛЬНО
Derived* d = dynamic_cast<Derived*>(ptr);
d->method(); // UB если d == nullptr!
Таблица проблем и исправлений
| № | Проблема | Симптом | Исправление |
|---|---|---|---|
| 1 | Нет виртуального деструктора | Утечка памяти, RTTI недоступна | Добавить virtual ~Base() = default; |
| 2 | print() не виртуальная | Вызывается Base::print() всегда | Добавить virtual к Base::print() |
| 3 | Нет проверки dynamic_cast | Разыменование nullptr → Segfault | Проверить if (d != nullptr) |
| 4 | Нет override ключевого слова | Легко забыть переопределить | Добавить override в Derived |
Когда использовать dynamic_cast
Используй когда:
- Нужно привести указатель/ссылку к более специфическому типу
- Не всегда ясно, какой конкретный тип объекта
- Нужна проверка типа во время выполнения
Избегай когда:
- Можно использовать virtual функции (100% случаев)
- Производительность критична (dynamic_cast медленнее)
Плохой паттерн:
// ❌ Частые dynamic_cast означают плохой дизайн
if (auto* a = dynamic_cast<ClassA*>(ptr)) {
a->methodA();
} else if (auto* b = dynamic_cast<ClassB*>(ptr)) {
b->methodB();
} else if (auto* c = dynamic_cast<ClassC*>(ptr)) {
c->methodC();
}
// Это делает virtual функцию virtual method()
Хороший паттерн:
// ✅ Используй virtual функции
class Base {
public:
virtual void method() = 0;
};
class A : public Base { void method() override { ... } };
class B : public Base { void method() override { ... } };
class C : public Base { void method() override { ... } };
// Просто вызвать:
ptr->method(); // вызовет нужный метод автоматически
Итоговый список ошибок в исходном коде
- ✗ Деструктор Base не виртуальный → утечка памяти
- ✗ Функция Base::print() не виртуальная → неправильный вызов
- ✗ Результат dynamic_cast не проверяется → nullptr dereference
- ✗ Нет override ключевого слова → непонятен intent
- ✗ Использование dynamic_cast где можна virtual function → плохой дизайн