Menu
Coddy logo textTech

Dynamic Casting и RTTI

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

Иногда при работе с полиморфизмом необходимо определить фактический тип объекта во время выполнения или безопасно преобразовать указатель на базовый класс в указатель на производный класс. C++ предоставляет RTTI (информация о типе во время выполнения) и 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: Define базовый Vehicle class, представляющий любое транспортное средство в системе:
    • Защищённый член std::string licensePlate
    • constructor, который инициализирует номерной знак
    • Виртуальный метод getDescription(), возвращающий "Vehicle: <licensePlate>"
    • Виртуальный destructor
  • Vehicles.h: Define три производных типа транспортных средств:

    Car:

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

    Truck:

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

    Motorcycle:

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

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

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

    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: определение фактического типа во время выполнения и соответствующее действие.

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

#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. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
    //   6. Вывести пустую строку между проверками каждого транспортного средства

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

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

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++