Dynamic Casting & RTTI
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 62번째.
다형성을 다룰 때 가끔, 런타임에 객체의 실제 타입을 확인하거나 기초 클래스 포인터를 파생 클래스 포인터로 안전하게 변환해야 할 때가 있습니다. 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: 시스템의 모든 차량을 나타내는 기본Vehicle클래스를 정의합니다.- protected
std::string licensePlate멤버 - 번호판을 초기화하는 생성자
"Vehicle: <licensePlate>"를 반환하는 가상getDescription()메서드- 가상 소멸자
- protected
Vehicles.h: 세 가지 파생 차량 유형을 정의합니다.Car:- private
int seatCount멤버 - 번호판과 좌석 수를 인자로 받는 생성자
"Car: <licensePlate>"를 반환하도록getDescription()오버라이드Inspecting <seatCount> seatbelts in <licensePlate>를 출력하는inspectSeatbelts()메서드
Truck:- private
double cargoCapacity멤버 (톤 단위) - 번호판과 화물 용량을 인자로 받는 생성자
"Truck: <licensePlate>"를 반환하도록getDescription()오버라이드Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>를 출력하는inspectCargo()메서드
Motorcycle:- private
bool hasSidecar멤버 - 번호판과 사이드카 유무를 인자로 받는 생성자
"Motorcycle: <licensePlate>"를 반환하도록getDescription()오버라이드- 사이드카가 있는 경우
Inspecting helmet storage in <licensePlate>를 출력하고, 없는 경우No helmet storage in <licensePlate>를 출력하는inspectHelmetStorage()메서드
- private
main.cpp: 세 개의 입력(각각 별도의 줄)을 읽습니다.- 승용차 번호판
- 트럭 번호판
- 오토바이 번호판
좌석이 4개인
Car, 용량이 10.5톤인Truck, 사이드카가 있는Motorcycle을 생성합니다. 세 가지 모두를Vehicle*포인터 배열에 저장합니다.배열을 순회하며 각 차량에 대해 다음을 수행합니다.
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의 힘입니다.
치트 시트
C++는 런타임에 객체의 실제 타입을 확인하고 상속 계층 구조 내에서 포인터를 안전하게 변환할 수 있도록 RTTI (Runtime Type Information, 런타임 타입 정보)와 dynamic_cast를 제공합니다.
dynamic_cast는 런타임 검사를 수행하며, 변환이 유효하지 않은 경우 nullptr을 반환합니다:
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); // 성공: 유효한 포인터 반환
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는 유용하지만, 이를 빈번하게 사용하는 것은 종종 설계상의 문제를 나타냅니다. 가능한 경우 가상 함수를 사용하는 것이 좋습니다.
직접 해보기
#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: 좌석이 4개인 Car 생성
// TODO: 적재 용량이 10.5톤인 Truck 생성
// TODO: 사이드카가 있는(true) Motorcycle 생성
// TODO: 세 객체 모두를 Vehicle* 포인터 배열에 저장
// TODO: 배열을 순회하며 각 차량에 대해 다음을 수행:
// 1. getDescription()을 사용하여 설명 출력
// 2. dynamic_cast를 사용하여 각 파생 타입으로 캐스팅 시도
// 3. Car*로의 캐스팅이 성공하면, inspectSeatbelts() 호출
// 4. Truck*로의 캐스팅이 성공하면, inspectCargo() 호출
// 5. Motorcycle*로의 캐스팅이 성공하면, inspectHelmetStorage() 호출
// 6. 각 차량의 점검 사이에 빈 줄 출력
// TODO: 동적으로 할당된 객체 정리
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.