Dynamic Casting e RTTI
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 62 de 104.
Às vezes, ao trabalhar com polimorfismo, você precisa determinar o tipo real de um objeto em tempo de execução ou converter com segurança um ponteiro de classe base para um ponteiro de classe derivada. O C++ fornece RTTI (Runtime Type Information) e dynamic_cast para essas situações.
dynamic_cast converte com segurança ponteiros ou referências dentro de uma hierarquia de herança. Ao contrário do static_cast, ele realiza uma verificação em tempo de execução e retorna nullptr se a conversão for inválida:
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); // Sucesso: retorna um ponteiro válido
if (dog) {
dog->bark(); // Seguro para chamar o método específico de Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Falha: retorna nullptrImportante: dynamic_cast só funciona com tipos polimórficos (classes com pelo menos uma função virtual). O operador typeid permite consultar o tipo real de um objeto:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Exibe informações de tipo para DogEmbora o dynamic_cast seja útil, o uso frequente muitas vezes indica um problema de design. Prefira funções virtuais quando possível, pois elas permitem que o objeto lide com o comportamento específico do tipo sem verificação de tipo explícita.
Desafio
FácilVamos construir um sistema de inspeção de veículos que utiliza dynamic_cast para identificar e interagir com segurança com diferentes tipos de veículos. Você criará uma hierarquia de veículos onde um inspetor precisa realizar verificações específicas de tipo que apenas certos veículos suportam.
Você organizará seu código em três arquivos:
Vehicle.h: Defina uma classe baseVehicleque representa qualquer veículo no sistema:- Um membro protegido
std::string licensePlate - Um construtor que inicializa a placa de licença
- Um método virtual
getDescription()que retorna"Vehicle: <licensePlate>" - Um destrutor virtual
- Um membro protegido
Vehicles.h: Defina três tipos de veículos derivados:Car:- Um membro privado
int seatCount - Um construtor que recebe a placa de licença e a contagem de assentos
- Sobrescreva
getDescription()para retornar"Car: <licensePlate>" - Um método
inspectSeatbelts()que imprime:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- Um membro privado
double cargoCapacity(em toneladas) - Um construtor que recebe a placa de licença e a capacidade de carga
- Sobrescreva
getDescription()para retornar"Truck: <licensePlate>" - Um método
inspectCargo()que imprime:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- Um membro privado
bool hasSidecar - Um construtor que recebe a placa de licença e o status do sidecar
- Sobrescreva
getDescription()para retornar"Motorcycle: <licensePlate>" - Um método
inspectHelmetStorage()que imprime:Inspecting helmet storage in <licensePlate>se tiver um sidecar, ouNo helmet storage in <licensePlate>se não tiver
- Um membro privado
main.cpp: Leia três entradas (cada uma em uma linha separada):- Placa de licença do carro
- Placa de licença do caminhão
- Placa de licença da motocicleta
Crie um
Carcom 4 assentos, umTruckcom 10.5 toneladas de capacidade e umaMotorcyclecom um sidecar. Armazene os três em um array de ponteirosVehicle*.Percorra o array e para cada veículo:
- Imprima sua descrição usando
getDescription() - Use
dynamic_castpara tentar converter para cada tipo derivado - Se a conversão para
Car*for bem-sucedida, chameinspectSeatbelts() - Se a conversão para
Truck*for bem-sucedida, chameinspectCargo() - Se a conversão para
Motorcycle*for bem-sucedida, chameinspectHelmetStorage()
Imprima uma linha em branco entre a inspeção de cada veículo. Limpe seus objetos alocados dinamicamente quando terminar.
Por exemplo, com as entradas 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-789Observe como o dynamic_cast retorna um ponteiro válido apenas quando o tipo real do objeto corresponde ao tipo de destino. Para cada veículo, apenas uma das três conversões será bem-sucedida, permitindo que você chame com segurança o método de inspeção específico do tipo. Este é o poder do RTTI — determinar o tipo real em tempo de execução e agir de acordo.
Folha de consulta
O C++ fornece RTTI (Runtime Type Information) e dynamic_cast para determinar o tipo real de um objeto em tempo de execução e converter ponteiros com segurança dentro de uma hierarquia de herança.
O dynamic_cast realiza uma verificação em tempo de execução e retorna nullptr se a conversão for inválida:
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Sucesso: retorna um ponteiro válido
if (dog) {
dog->bark(); // Seguro para chamar método específico de Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // Falha: retorna nullptrImportante: o dynamic_cast só funciona com tipos polimórficos (classes com pelo menos uma função virtual).
O operador typeid consulta o tipo real de um objeto:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Exibe informações de tipo para DogEmbora útil, o uso frequente de dynamic_cast geralmente indica um problema de design. Prefira funções virtuais quando possível.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// Ler as entradas
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: Criar um Car com 4 assentos
// TODO: Criar um Truck com 10.5 toneladas de capacidade
// TODO: Criar uma Motorcycle com um sidecar (true)
// TODO: Armazenar todos os três em um array de ponteiros Vehicle*
// TODO: Percorrer o array e para cada veículo:
// 1. Imprimir sua descrição usando getDescription()
// 2. Usar dynamic_cast para tentar converter para cada tipo derivado
// 3. Se a conversão para Car* for bem-sucedida, chamar inspectSeatbelts()
// 4. Se a conversão para Truck* for bem-sucedida, chamar inspectCargo()
// 5. Se a conversão para Motorcycle* for bem-sucedida, chamar inspectHelmetStorage()
// 6. Imprimir uma linha em branco entre a inspeção de cada veículo
// TODO: Limpar os objetos alocados dinamicamente
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico