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

Найти проблемы в коде (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;
}

Вопросы

  1. Скомпилируется ли этот код? Если нет, какая ошибка?
  2. Если скомпилируется, что произойдёт при выполнении?
  3. Какие проблемы есть в коде?
  4. Как исправить код, чтобы он работал корректно?

Подсказки

  • Обратите внимание на требования к 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;
2print() не виртуальнаяВызывается 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();  // вызовет нужный метод автоматически

Итоговый список ошибок в исходном коде

  1. ✗ Деструктор Base не виртуальный → утечка памяти
  2. ✗ Функция Base::print() не виртуальная → неправильный вызов
  3. ✗ Результат dynamic_cast не проверяется → nullptr dereference
  4. ✗ Нет override ключевого слова → непонятен intent
  5. ✗ Использование dynamic_cast где можна virtual function → плохой дизайн