Dynamic casting e RTTI
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 62 di 104.
Talvolta, quando lavori con il polimorfismo, devi determinare il tipo effettivo di un oggetto durante l'esecuzione oppure convertire in modo sicuro un puntatore a una classe base in un puntatore a una classe derivata. C++ fornisce RTTI (Runtime Type Information) e dynamic_cast per queste situazioni.
dynamic_cast converte in modo sicuro puntatori o riferimenti all'interno di una gerarchia di ereditarietà. A differenza di static_cast, esegue un controllo a runtime e restituisce nullptr se la conversione non è valida:
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); // Riesce: restituisce un puntatore valido
if (dog) {
dog->bark(); // È sicuro chiamare il metodo specifico di Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Non riesce: restituisce nullptrImportante: dynamic_cast funziona solo con tipi polimorfici (classi con almeno una funzione virtuale). L'operatore typeid ti consente di verificare il tipo effettivo di un oggetto:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Mostra informazioni sul tipo di DogSebbene dynamic_cast sia utile, il suo uso frequente spesso indica un problema di progettazione. Quando possibile, preferisci le funzioni virtuali, perché permettono all'oggetto di gestire comportamenti specifici del tipo senza controlli espliciti del tipo.
Sfida
FacileCostruiamo un sistema di ispezione dei veicoli che usa dynamic_cast per identificare e gestire in sicurezza diversi tipi di veicoli. Creerai una gerarchia di veicoli in cui un ispettore deve eseguire controlli specifici per tipo, supportati solo da alcuni veicoli.
Organizzerai il codice in tre file:
Vehicle.h: definisci una classe baseVehicleche rappresenta qualsiasi veicolo nel sistema:- Un membro protetto
std::string licensePlate - Un costruttore che inizializza la targa
- Un metodo virtuale
getDescription()che restituisce"Vehicle: <licensePlate>" - Un distruttore virtuale
- Un membro protetto
Vehicles.h: definisci tre tipi di veicoli derivati:Car:- Un membro privato
int seatCount - Un costruttore che accetta la targa e il numero di posti
- Sovrascrivi
getDescription()in modo che restituisca"Car: <licensePlate>" - Un metodo
inspectSeatbelts()che stampa:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Un membro privato
double cargoCapacity(in tonnellate) - Un costruttore che accetta la targa e la capacità di carico
- Sovrascrivi
getDescription()in modo che restituisca"Truck: <licensePlate>" - Un metodo
inspectCargo()che stampa:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Un membro privato
bool hasSidecar - Un costruttore che accetta la targa e indica se è presente un sidecar
- Sovrascrivi
getDescription()in modo che restituisca"Motorcycle: <licensePlate>" - Un metodo
inspectHelmetStorage()che stampa:Inspecting helmet storage in <licensePlate>se il veicolo ha un sidecar, oppureNo helmet storage in <licensePlate>se non ce l'ha
- Un membro privato
main.cpp: leggi tre input (ciascuno su una riga separata):- Targa dell'auto
- Targa del camion
- Targa della motocicletta
Crea una
Carcon 4 posti, unTruckcon una capacità di carico di 10.5 tonnellate e unaMotorcyclecon un sidecar. Memorizzali tutti e tre in un array di puntatoriVehicle*.Scorri l'array e, per ogni veicolo:
- Stampa la descrizione usando
getDescription() - Usa
dynamic_castper tentare il cast a ciascun tipo derivato - Se il cast a
Car*riesce, chiamainspectSeatbelts() - Se il cast a
Truck*riesce, chiamainspectCargo() - Se il cast a
Motorcycle*riesce, chiamainspectHelmetStorage()
Stampa una riga vuota tra l'ispezione di ciascun veicolo. Al termine, libera gli oggetti allocati dinamicamente.
Per esempio, con gli input ABC-123, TRK-456 e 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-789Nota che dynamic_cast restituisce un puntatore valido solo quando il tipo effettivo dell'oggetto corrisponde al tipo di destinazione. Per ogni veicolo, solo uno dei tre cast avrà successo, consentendoti di chiamare in sicurezza il metodo di ispezione specifico per quel tipo. Questa è la potenza di RTTI: determinare il tipo effettivo durante l'esecuzione e agire di conseguenza.
Provalo tu
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Leggi gli input
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Crea un Car con 4 posti
// TODO: Crea un Truck con una capacità di 10.5 tonnellate
// TODO: Crea un Motorcycle con sidecar (true)
// TODO: Memorizza tutti e tre in un array di puntatori Vehicle*
// TODO: Scorri l'array e, per ogni veicolo:
// 1. Stampa la sua descrizione usando getDescription()
// 2. Usa dynamic_cast per tentare la conversione a ciascun tipo derivato
// 3. Se la conversione a Car* riesce, chiama inspectSeatbelts()
// 4. Se la conversione a Truck* riesce, chiama inspectCargo()
// 5. Se la conversione a Motorcycle* riesce, chiama inspectHelmetStorage()
// 6. Stampa una riga vuota tra le ispezioni dei veicoli
// TODO: libera gli oggetti allocati dinamicamente
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online