Rzutowanie dynamiczne i RTTI
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 62 z 104.
Czasami podczas pracy z polimorfizmem trzeba określić rzeczywisty typ obiektu w czasie wykonywania programu lub bezpiecznie przekonwertować wskaźnik do klasy bazowej na wskaźnik do klasy pochodnej. C++ udostępnia RTTI (informacje o typie w czasie wykonywania) oraz dynamic_cast w takich sytuacjach.
dynamic_cast bezpiecznie konwertuje wskaźniki lub referencje w hierarchii dziedziczenia. W przeciwieństwie do static_cast wykonuje kontrolę w czasie działania i zwraca nullptr, jeśli konwersja jest nieprawidłowa:
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); // Udaje się: zwraca prawidłowy wskaźnik
if (dog) {
dog->bark(); // Można bezpiecznie wywołać metodę specyficzną dla Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Nie udaje się: zwraca nullptrWażne: dynamic_cast działa tylko z typami polimorficznymi (klasami mającymi co najmniej jedną funkcję wirtualną). Operator typeid umożliwia sprawdzenie rzeczywistego typu obiektu:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Wyświetla informacje o typie DogChociaż dynamic_cast jest przydatny, jego częste używanie często wskazuje na problem z projektem. Jeśli to możliwe, preferuj funkcje wirtualne, ponieważ pozwalają obiektowi obsługiwać zachowanie zależne od typu bez jawnego sprawdzania typu.
Wyzwanie
ŁatwyZbudujmy system kontroli pojazdów, który używa dynamic_cast do bezpiecznego identyfikowania różnych typów pojazdów i interakcji z nimi. Utworzysz hierarchię pojazdów, w której inspektor musi przeprowadzać kontrole specyficzne dla typu, obsługiwane tylko przez niektóre pojazdy.
Rozmieścisz kod w trzech plikach:
Vehicle.h: Zdefiniuj bazową klasęVehicle, która reprezentuje dowolny pojazd w systemie:- Pole
std::string licensePlateo dostępie chronionym - Konstruktor inicjalizujący numer rejestracyjny
- Wirtualna metoda
getDescription(), która zwraca"Vehicle: <licensePlate>" - Wirtualny destruktor
- Pole
Vehicles.h: Zdefiniuj trzy pochodne typy pojazdów:Car:- Prywatne pole
int seatCount - Konstruktor przyjmujący numer rejestracyjny i liczbę miejsc
- Nadpisz
getDescription(), aby zwracała"Car: <licensePlate>" - Metoda
inspectSeatbelts(), która wyświetla:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Prywatne pole
double cargoCapacity(w tonach) - Konstruktor przyjmujący numer rejestracyjny i ładowność
- Nadpisz
getDescription(), aby zwracała"Truck: <licensePlate>" - Metoda
inspectCargo(), która wyświetla:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Prywatne pole
bool hasSidecar - Konstruktor przyjmujący numer rejestracyjny i informację o obecności bocznego wózka
- Nadpisz
getDescription(), aby zwracała"Motorcycle: <licensePlate>" - Metoda
inspectHelmetStorage(), która wyświetlaInspecting helmet storage in <licensePlate>, jeśli pojazd ma boczny wózek, lubNo helmet storage in <licensePlate>, jeśli go nie ma
- Prywatne pole
main.cpp: Wczytaj trzy dane wejściowe (każde w osobnym wierszu):- Numer rejestracyjny samochodu
- Numer rejestracyjny ciężarówki
- Numer rejestracyjny motocykla
Utwórz obiekt
Carz 4 miejscami, obiektTrucko ładowności 10.5 ton oraz obiektMotorcyclez bocznym wózkiem. Umieść wszystkie trzy w tablicy wskaźnikówVehicle*.Przejdź przez tablicę i dla każdego pojazdu:
- Wyświetl jego opis za pomocą
getDescription() - Użyj
dynamic_cast, aby spróbować rzutować wskaźnik na każdy z typów pochodnych - Jeśli rzutowanie na
Car*się powiedzie, wywołajinspectSeatbelts() - Jeśli rzutowanie na
Truck*się powiedzie, wywołajinspectCargo() - Jeśli rzutowanie na
Motorcycle*się powiedzie, wywołajinspectHelmetStorage()
Wyświetl pusty wiersz między kontrolą każdego pojazdu. Po zakończeniu zwolnij dynamicznie przydzielone obiekty.
Na przykład dla danych wejściowych ABC-123, TRK-456 i 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-789Zwróć uwagę, że dynamic_cast zwraca prawidłowy wskaźnik tylko wtedy, gdy rzeczywisty typ obiektu jest zgodny z typem docelowym. Dla każdego pojazdu powiedzie się tylko jedno z trzech rzutowań, co pozwala bezpiecznie wywołać metodę kontroli właściwą dla danego typu. Na tym polega siła RTTI: ustalaniu rzeczywistego typu w czasie działania programu i odpowiednim działaniu.
Spróbuj swoich sił
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Utwórz obiekt Car z 4 miejscami
// TODO: Utwórz obiekt Truck o ładowności 10.5 tony
// TODO: Utwórz obiekt Motorcycle z wózkiem bocznym (true)
// TODO: Umieść wszystkie trzy obiekty w tablicy wskaźników Vehicle*
// TODO: Przejdź przez tablicę i dla każdego pojazdu:
// 1. Wypisz jego opis za pomocą getDescription()
// 2. Użyj dynamic_cast, aby spróbować rzutowania na każdy typ pochodny
// 3. Jeśli rzutowanie na Car* się powiedzie, wywołaj inspectSeatbelts()
// 4. Jeśli rzutowanie na Truck* się powiedzie, wywołaj inspectCargo()
// 5. Jeśli rzutowanie na Motorcycle* się powiedzie, wywołaj inspectHelmetStorage()
// 6. Wypisz pustą linię między kontrolami kolejnych pojazdów
// TODO: Zwolnij pamięć zajmowaną przez dynamicznie alokowane obiekty
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online