Menu
Coddy logo textTech

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 nullptr

Importante: 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 Dog

Sebbene 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.

challenge icon

Sfida

Facile

Costruiamo 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 base Vehicle che 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
  • 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, oppure No helmet storage in <licensePlate> se non ce l'ha
  • main.cpp: leggi tre input (ciascuno su una riga separata):
    1. Targa dell'auto
    2. Targa del camion
    3. Targa della motocicletta

    Crea una Car con 4 posti, un Truck con una capacità di carico di 10.5 tonnellate e una Motorcycle con un sidecar. Memorizzali tutti e tre in un array di puntatori Vehicle*.

    Scorri l'array e, per ogni veicolo:

    1. Stampa la descrizione usando getDescription()
    2. Usa dynamic_cast per tentare il cast a ciascun tipo derivato
    3. Se il cast a Car* riesce, chiama inspectSeatbelts()
    4. Se il cast a Truck* riesce, chiama inspectCargo()
    5. Se il cast a Motorcycle* riesce, chiama inspectHelmetStorage()

    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-789

Nota 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;
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore C++ online