Menu
Coddy logo textTech

Dynamic Casting и RTTI

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 62 из 104.

Иногда при работе с полиморфизмом вам необходимо определить фактический тип объекта во время выполнения или безопасно преобразовать указатель базового класса в указатель производного класса. C++ предоставляет RTTI (Runtime Type Information) и dynamic_cast для таких ситуаций.

dynamic_cast безопасно преобразует указатели или ссылки внутри иерархии наследования. В отличие от static_cast, он выполняет проверку во время выполнения и возвращает nullptr, если преобразование недопустимо:

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void bark() { std::cout << "Woof!" << std::endl; }
};

class Cat : public Animal {};

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);  // Успешно: возвращает валидный указатель

if (dog) {
    dog->bark();  // Безопасно вызывать метод, специфичный для Dog
}

Cat* cat = dynamic_cast<Cat*>(animal);  // Ошибка: возвращает nullptr

Важно: dynamic_cast работает только с полиморфными типами (классами, имеющими хотя бы одну виртуальную функцию). Оператор typeid позволяет запрашивать фактический тип объекта:

#include <typeinfo>

Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl;  // Выводит информацию о типе Dog

Хотя dynamic_cast полезен, его частое использование часто указывает на проблему в проектировании. Отдавайте предпочтение виртуальным функциям, когда это возможно, так как они позволяют объекту обрабатывать специфичное для типа поведение без явной проверки типов.

challenge icon

Задание

Легко

Давайте создадим систему инспекции транспортных средств, которая использует dynamic_cast для безопасной идентификации и взаимодействия с различными типами транспортных средств. Вы создадите иерархию транспортных средств, в которой инспектору необходимо выполнять специфические для каждого типа проверки, которые поддерживают только определенные транспортные средства.

Вы распределите свой код по трем файлам:

  • Vehicle.h: Определите базовый класс Vehicle, который представляет любое транспортное средство в системе:
    • Защищенный член std::string licensePlate
    • Конструктор, инициализирующий номерной знак
    • Виртуальный метод getDescription(), который возвращает "Vehicle: <licensePlate>"
    • Виртуальный деструктор
  • Vehicles.h: Определите три производных типа транспортных средств:

    Car:

    • Приватный член int seatCount
    • Конструктор, принимающий номерной знак и количество мест
    • Переопределите getDescription(), чтобы он возвращал "Car: <licensePlate>"
    • Метод inspectSeatbelts(), который выводит: Inspecting <seatCount> seatbelts in <licensePlate>

    Truck:

    • Приватный член double cargoCapacity (в тоннах)
    • Конструктор, принимающий номерной знак и грузоподъемность
    • Переопределите getDescription(), чтобы он возвращал "Truck: <licensePlate>"
    • Метод inspectCargo(), который выводит: Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>

    Motorcycle:

    • Приватный член bool hasSidecar
    • Конструктор, принимающий номерной знак и наличие коляски
    • Переопределите getDescription(), чтобы он возвращал "Motorcycle: <licensePlate>"
    • Метод inspectHelmetStorage(), который выводит: Inspecting helmet storage in <licensePlate>, если есть коляска, или No helmet storage in <licensePlate>, если ее нет
  • main.cpp: Считайте три входных значения (каждое на отдельной строке):
    1. Номерной знак автомобиля
    2. Номерной знак грузовика
    3. Номерной знак мотоцикла

    Создайте Car с 4 местами, Truck с грузоподъемностью 10.5 тонн и Motorcycle с коляской. Сохраните все три объекта в массиве указателей Vehicle*.

    Пройдите циклом по массиву и для каждого транспортного средства:

    1. Выведите его описание с помощью getDescription()
    2. Используйте dynamic_cast для попытки приведения к каждому производному типу
    3. Если приведение к Car* прошло успешно, вызовите inspectSeatbelts()
    4. Если приведение к Truck* прошло успешно, вызовите inspectCargo()
    5. Если приведение к Motorcycle* прошло успешно, вызовите inspectHelmetStorage()

    Выводите пустую строку между проверками каждого транспортного средства. Очистите динамически выделенные объекты по завершении работы.

Например, при входных данных ABC-123, TRK-456 и MTR-789:

Car: ABC-123
Inspecting 4 seatbelts in ABC-123

Truck: TRK-456
Inspecting cargo area (10.5 tons) in TRK-456

Motorcycle: MTR-789
Inspecting helmet storage in MTR-789

Обратите внимание, как dynamic_cast возвращает валидный указатель только тогда, когда фактический тип объекта совпадает с целевым типом. Для каждого транспортного средства только одно из трех приведений типов будет успешным, что позволит вам безопасно вызвать метод проверки, специфичный для данного типа. В этом заключается сила RTTI — определение фактического типа во время выполнения и соответствующие действия.

Шпаргалка

C++ предоставляет RTTI (Runtime Type Information — информация о типе во время выполнения) и dynamic_cast для определения фактического типа объекта во время выполнения и безопасного преобразования указателей внутри иерархии наследования.

dynamic_cast выполняет проверку во время выполнения и возвращает nullptr, если преобразование недопустимо:

class Animal {
public:
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void bark() { std::cout << "Woof!" << std::endl; }
};

Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal);  // Успешно: возвращает валидный указатель

if (dog) {
    dog->bark();  // Безопасно вызывать метод, специфичный для Dog
}

Cat* cat = dynamic_cast<Cat*>(animal);  // Ошибка: возвращает nullptr

Важно: dynamic_cast работает только с полиморфными типами (классами, имеющими хотя бы одну виртуальную функцию).

Оператор typeid запрашивает фактический тип объекта:

#include <typeinfo>

Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl;  // Выводит информацию о типе для Dog

Хотя dynamic_cast и полезен, его частое использование часто указывает на проблемы в проектировании. По возможности отдавайте предпочтение виртуальным функциям.

Попробуйте сами

#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"

using namespace std;

int main() {
    // Чтение входных данных
    string carPlate, truckPlate, motorcyclePlate;
    cin >> carPlate;
    cin >> truckPlate;
    cin >> motorcyclePlate;

    // TODO: Создайте Car с 4 сиденьями
    // TODO: Создайте Truck грузоподъемностью 10.5 тонн
    // TODO: Создайте Motorcycle с коляской (true)

    // TODO: Сохраните все три объекта в массив указателей Vehicle*

    // TODO: Пройдите циклом по массиву и для каждого транспортного средства:
    //   1. Выведите его описание с помощью getDescription()
    //   2. Используйте dynamic_cast для попытки приведения к каждому производному типу
    //   3. Если приведение к Car* прошло успешно, вызовите inspectSeatbelts()
    //   4. Если приведение к Truck* прошло успешно, вызовите inspectCargo()
    //   5. Если приведение к Motorcycle* прошло успешно, вызовите inspectHelmetStorage()
    //   6. Выведите пустую строку между проверками каждого транспортного средства

    // TODO: Очистите динамически выделенные объекты

    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование