[C++] 상속에서의 형변환 - RTTI와 dynamic_cast

2024. 6. 28. 16:33·Programming Language(Sub)/C++

상속에서의 형변환 - RTTI와 dynamic_cast

  • static_cast: 정적인 시간(컴파일 타임)에 캐스팅이 일어남

  • dynamic_cast : 동적인 시간(런타임)에 캐스팅이 일어남

복습! static cast

class Base {
   public:
    int x;
};

class Derived : public Base {
   public:
    int y;
};

int main() {
    Base *b = new Derived;
    Derived *d = static_cast<Derived*>(b);
}
  • b가 가리키는 인스턴스의 타입을 알고 있다면, static_cast를 이용하여 형변환을 해줄 수 있었다

RTTI(Run Time Type Information/Identification)

  • b가 실제로 어떤 타입을 가리키는지 컴파일 타임에 알기 어려운 경우가 많다

    • 런타임에 의존하는 경우가 발생
  • 런타임에 포인터가 가리키는 객체의 타입을 알 수 있게끔 해주는 기능을 RTTI라고 한다

  • 사실 C++에서 기본적으로 포인터가 가리키는 객체의 타입을 런타임에 확인할 수 있는 방법은 없다

    • 그런데 C++ 내부에서 RTTI가 필요할 수 밖에 없는 경우가 딱 한 가지 존재한다

      • 가상함수를 사용했을 경우이다
class Base {
   public:
    void f() {}
    int x;
};

class Derived : public Base {
   public:
    void f() {}
    int y;
};

int main() {
    Base *b = new Derived;
    Derived *d = static_cast<Derived*>(b);
    b->f();
}
  • 이 경우에는 f()가 가상 함수로 선언되어있지 않기 때문에 정적 바인딩이 일어난다
class Base { // 다형 클래스
   public:
    virtual void f() {}
        int x;
};

class Derived : public Base {
   public:
    void f() {}
    int y;
};

int main() {
    Base *b = new Derived;
    Derived *d = static_cast<Derived*>(b);
    b->f();
}
  • 만약 virtual로 f()를 선언하면 컴파일러는 b->f()를 건들지 않는다

    • 이게 가능하려면 런타임에 b가 가리키는 객체의 타입이 무엇인지를 알아올 수 있는 방법이 있어야 동적 바인딩이 가능하다

    • RTTI가 필요하다.

  • 가상 함수가 하나라도 있는 클래스를 다형 클래스라고 한다

  • 일단 virtual을 빼서 다형 클래스가 아닌 클래스로 만들어보자

#include <iostream>
using namespace std;
class Base {
   public:
    void f() {}
        int x;
};

class Derived : public Base {
   public:
    void f() {}
    int y;
};

int main() {
        cout << sizeof(Base) << endl;
        cout << sizeof(Derived) << endl;
}
  • Base의 크기는 4바이트, Derived의 크기는 8바이트로 예상할 수 있다.

  • 여기서 virtual을 추가하면

#include <iostream>
using namespace std;
class Base {
   public:
    void f() {}
        int x;
};

class Derived : public Base {
   public:
    void f() {}
    int y;
};

int main() {
        cout << sizeof(Base) << endl;
        cout << sizeof(Derived) << endl;
}
  • 크기가 8, 12로 출력된다. 4바이트가 더 늘어났다.

    • 이 4바이트는 기본적으로 어딘가를 가리키는 포인터다. 이 포인터에 클래스에 대한 정보가 담겨있다.

      • Derived가 생성되면, Derived 타입의 정보를 담고 있는 메모리 공간을 4바이트 포인터가 가리키게 된다.

      • Base가 생성되면, Base 타입의 정보를 담고 있는 메모리 공간을 4바이트 포인터가 가리키게 된다.

  • Base *b = new Derived; 라면 어떻게 동작할까?

    • 포인터 b는 Base* 이므로 8바이트 공간을 가리키는 포인터이다.

    • 여기서 RTTI를 실현하기 위해, 4바이트 포인터가 가리키는 곳을 따라가면 Derived라는 타입의 정보를 담는 공간이 나온다.

    • 그래서, 실제로 가리키는 객체가 Base처럼 생겼지만 실제로는 Derived 타입이라는 것을 확인한다.

    • 이것이 기본적인 RTTI의 원리이다.

Dynamic cast

  • dynamic_cast는 런타임에 타입에 대한 정보를 가지고 있어야 하기 때문에, RTTI가 지원되는 경우에만 dynamic cast를 사용해서 다운 캐스팅을 할 수가 있는 것이다.

  • 따라서 Base로부터 Derived로 dynamic cast를 하고 싶다면, Base 클래스는 반드시 가상 함수를 포함하는 다형 클래스여야 한다

int main() {
        cout << sizeof(Base) << endl;
        cout << sizeof(Derived) << endl;
    int *a = (int*)new Base;
    cout << a[0] << " " << a[1] << endl;
    int *b = (int*)new Derived;
    cout << b[0] << " " << b[1] << " " << b[2] << endl;
    int *c = (int*)new Derived;
    cout << c[0] << " " << c[1] << " " << c[2] << endl;
        delete a;
        delete b;
        delete c;
}
  • (int*)new Base는 8바이트 메모리 공간을 가리키는 포인터가 첫 4바이트를 보라는 의미

    • a[0]는 첫 4바이트 공간, a[1]은 두 번째 4바이트 공간
  • 실행결과

8
12
2661172 10
2661184 10 20
2661184 10 20

  • 첫 4바이트 공간에는 2661172(Base 클래스 정보), 2661184(Derived 클래스 정보)가 들어가 있다.

Example) Shaple Class

#include <iostream>
#include <math.h>

using namespace std;

class Shape {
public:
    virtual double GetArea() const = 0;
    virtual void Resize(double k) = 0;
};

class Circle : public Shape {
public:
    Circle(double r) : r(r) {}
    double GetArea() const {
        return r * r * 3.14;
    }
    void Resize(double k) {
        r *= k;
    }
private:
    double r;
};

class Rectangle : public Shape {
public:
    Rectangle(double a, double b) : a(a), b(b) {}
    double GetArea() const {
        return a * b;
    }
    void Resize(double k) {
        a *= k;
        b *= k;
    }
    double GetDiag() const {
        return sqrt(a * a + b * b);
    }
private:
    double a, b;
};

int main() {
    Shape *shapes[] = { new Circle(1), new Rectangle(1, 2) }; // 포인터 배열

    for (int i = 0; i < 2; i++) {
        // 도형의 넓이
        // 만약 직사각형일 경우, 대각선 길이 출력
        cout << "도형의 넓이:" << shapes[i]->GetArea() << endl;
        Rectangle *r = dynamic_cast<Rectangle*>(shapes[i]);
    }
    for (int i = 0; i < 2; i++) {
        delete shapes[i];
    }
}
  • 만약 shapes를 Rectangle로 static_cast를 했다면 shape를 Rectangle로 가정해서 캐스팅하기 때문에 컴파일 과정에서는 오류가 발생하지 않았을 것이다.

    • 하지만 r이 실제로 가리키는 객체가 Rectangle인지 Circle인지는 런타임에만 알 수 있으므로 dynamic_cast를 사용하자

    • dynamic cast를 사용하면 가리키는 객체가 Rectangle일때만 캐스팅에 성공한다

      • 형변환에 실패하면 NULL이 r에 들어가게 된다
    • 그래서 if문에서 NULL에 대한 처리를 추가해줘야 한다.

int main() {
    Shape *shapes[] = { new Circle(1), new Rectangle(1, 2) };
    for (Shape *s : shapes) {
        // 도형의 넓이
        // 만약 직사각형일 경우, 대각선 길이 출력
        cout << "도형의 넓이:" << s->GetArea() << endl;
        Rectangle *r = dynamic_cast<Rectangle*>(s);
        if (r! = NULL) {
            cout << "대각선의 길이:" << s->GetDiag() << endl;
        }
    }
    for (Shape *s : shapes) {
        delete s;
    }
}
  • Dynamic cast는 RTTI 작업을 거쳐야 하기 때문에 성능 측면에서 좋지는 않다

Dynamic cast를 사용하지 않고 Shape Class 구현

#include <iostream>
#include <string>
#include <math.h>

using namespace std;

class Shape {
public:
    virtual ~Shape() {}
    virtual double GetArea() const = 0;
    virtual void Resize(double k) = 0;
    virtual string GetInfo() const {
        return "도형의 넓이 : " + to_string(GetArea())
    }
};

class Circle : public Shape {
public:
    Circle(double r) : r(r) {}
    double GetArea() const {
        return r * r * 3.14;
    }
    void Resize(double k) {
        r *= k;
    }
private:
    double r;
};

class Rectangle : public Shape {
public:
    Rectangle(double a, double b) : a(a), b(b) {}
    double GetArea() const {
        return a * b;
    }
    void Resize(double k) {
        a *= k;
        b *= k;
    }
    double GetDiag() const {
        return sqrt(a * a + b * b);
    }
    string GetInfo() const {
        return Shape::GetInfo() + "\n대각선의 길이: " + to_string(GetDiag());
    }
private:
    double a, b;
};

int main() {
    Shape *shapes[] = { new Circle(1), new Rectangle(1, 2) };
    for (Shape *s : shapes) {
        // 도형의 넓이
        // 만약 직사각형일 경우, 대각선 길이 출력
        cout << "도형의 넓이:" << s->GetArea() << endl;
        Rectangle *r = dynamic_cast<Rectangle*>(s);
        if (r! = NULL) {
            cout << "대각선의 길이:" << s->GetDiag() << endl;
        }
    }
    for (Shape *s : shapes) {
        delete s;
    }
}
  • GetInfo 함수를 가상 함수로 만들어서 각 인스턴스의 타입에 맞는 정보를 출력하도록 구현하였다.

참조) 두들낙서 C/C++ 강좌

'Programming Language(Sub) > C++' 카테고리의 다른 글

[C++] 다중 상속과 다이아몬드 문제  (0) 2024.06.28
[C++] 객체 지향 프로그래밍의 4대 원칙(원리)  (0) 2024.06.28
[C++] 상속에서의 형변환 - 다운캐스팅  (0) 2024.06.27
[C++] 상속에서의 형변환 - 업캐스팅(Upcasting)  (0) 2024.06.27
[C++] 생성/소멸자 실행 순서와 가상 소멸자  (0) 2024.06.27
'Programming Language(Sub)/C++' 카테고리의 다른 글
  • [C++] 다중 상속과 다이아몬드 문제
  • [C++] 객체 지향 프로그래밍의 4대 원칙(원리)
  • [C++] 상속에서의 형변환 - 다운캐스팅
  • [C++] 상속에서의 형변환 - 업캐스팅(Upcasting)
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (456)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (129)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (5)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[C++] 상속에서의 형변환 - RTTI와 dynamic_cast
상단으로

티스토리툴바