Menu
Coddy logo textTech

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는 유용하지만, 빈번한 사용은 종종 설계상의 문제를 나타냅니다. 가능하다면 가상 함수를 선호하세요. 가상 함수는 객체가 명시적인 타입 체크 없이 타입별 동작을 처리할 수 있게 해주기 때문입니다.

challenge icon

챌린지

쉬움

dynamic_cast를 사용하여 다양한 차량 유형을 안전하게 식별하고 상호 작용하는 차량 검사 시스템을 구축해 보겠습니다. 검사관이 특정 차량만 지원하는 유형별 점검을 수행해야 하는 차량 계층 구조를 만들 것입니다.

코드는 다음 세 개의 파일로 구성됩니다.

  • Vehicle.h: 시스템의 모든 차량을 나타내는 기본 Vehicle 클래스를 정의합니다.
    • protected std::string licensePlate 멤버
    • 번호판을 초기화하는 생성자
    • "Vehicle: <licensePlate>"를 반환하는 가상 getDescription() 메서드
    • 가상 소멸자
  • 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() 메서드
  • main.cpp: 세 개의 입력(각각 별도의 줄)을 읽습니다.
    1. 승용차 번호판
    2. 트럭 번호판
    3. 오토바이 번호판

    좌석이 4개인 Car, 용량이 10.5톤인 Truck, 사이드카가 있는 Motorcycle을 생성합니다. 세 가지 모두를 Vehicle* 포인터 배열에 저장합니다.

    배열을 순회하며 각 차량에 대해 다음을 수행합니다.

    1. getDescription()을 사용하여 설명을 출력합니다.
    2. dynamic_cast를 사용하여 각 파생 유형으로 캐스팅을 시도합니다.
    3. Car*로의 캐스팅이 성공하면 inspectSeatbelts()를 호출합니다.
    4. Truck*로의 캐스팅이 성공하면 inspectCargo()를 호출합니다.
    5. 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;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨