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 полезен, его частое использование часто указывает на проблему в дизайне. По возможности отдавай предпочтение виртуальным функциям, поскольку они позволяют объекту обрабатывать поведение, специфичное для типа, без явной проверки типа.
Задание
ЛегкоДавайте создадим систему проверки транспортных средств, которая использует dynamic_cast для безопасного определения и взаимодействия с различными типами транспортных средств. Вы создадите иерархию транспортных средств, в которой инспектору нужно выполнять проверки, специфичные для типа, поддерживаемые только определёнными транспортными средствами.
Вы организуете код в трёх файлах:
Vehicle.h: Define базовыйVehicleclass, представляющий любое транспортное средство в системе:- Защищённый член
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: Считать три значения (каждое в отдельной строке):- Номерной знак Car
- Номерной знак Truck
- Номерной знак Motorcycle
Создать
Carс 4 местами,Truckс грузоподъёмностью 10.5 тонны иMotorcycleс боковым прицепом. Сохранить все три объекта в array указателейVehicle*.Пройти в цикле по array и для каждого транспортного средства:
- Вывести его описание с помощью
getDescription() - Использовать
dynamic_cast, чтобы попытаться выполнить приведение к каждому производному типу - Если приведение к
Car*успешно, вызватьinspectSeatbelts() - Если приведение к
Truck*успешно, вызватьinspectCargo() - Если приведение к
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;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++