ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C/C++ 강좌] 84강. 상속에서의 형변환 (3) - RTTI와 dynamic_cast
    C++ 2022. 1. 20. 19:11

    https://www.youtube.com/watch?v=miIHJGasPsc&list=PLlJhQXcLQBJqywc5dweQ75GBRubzPxhAk&index=89

     

     

    1. RTTI (Run Time Type Inforrmaiton/Identification)

    우리가 가상함수를 통해서 동적바인딩을 할 수 있었다. 그리고 가상함수의 기능을 사용하기 위해서 RTTI가 필요하다.

    C++에는 2가지 종류의 클래스가 있다.

    첫 번째는 가상함수가 없는 일반적인 클래스이고,

    두 번째는 가상함수가 있는 클래스로 다형클래스라고 부른다.

     

    #include <iostream>
    using namespace std;
    
    class Base {
    public:
    	virtual void f() {} // 가상함수
    	int x;
    };
    
    class Derived : public Base {
    public:
    	void f() {}
    	int y;
     };
    
    int main() {
    	cout << sizeof(Base) << endl;
    	cout << sizeof(Derived) << endl;
    }

    위의 출력결과를 예상해보면 Base는 int형 멤버변수가 1개이므로 4바이트가 예상되고 Derived는 상속받은 int형 멤버변수1개 독립적인 int형 멤버변수1개로 총 8바이트가 예상된다.

    하지만 실제로 출력해보면 8과 12가 나온다.

    결과로 보아 멤버 변수와는 별개로 또 다른 4바이트의 공간이 존재하고 있다는 걸 알 수 있다.

    기본적으로 4바이트가 하는 역할은 어딘가를 가리키는 포인터인데 그 포인터의 주소값을 찾아가보면 이런 클래스들에 대한 정보들이 담겨져있다.

     

    그래서 정리해서 각 클래스의 크기를 살펴보자면

    Base의 멤버변수 : 자신의 클래스를 가리키는 포인터, Base::int x ( 총 8바이트 )

    Derived의 멤버변수 : 자신의 클래스를 가리키는 포인터, Base::int x, Derived::int y (총 12바이트)

     

    그리고 RTTI는 업캐스팅으로 정의되었더라도 자신의 클래스를 가리키는 포인터를 따라가서 실제로 어떤 클래스의 정보가 저장이 되었는지 판별하는 역할을 한다.

     

    2. dynamic_cast

    #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]);
    		if (r != NULL) {
    			cout << "대각선의 길이 : " << r->GetDiag() << endl;
    		}
    	}
    
    	for (int i = 0; i < 2; i++) {
    		delete shapes[i];
    	}
    }

    이 코드는 Shape클래스를 상속받는 Circle클래스와 Rectangle클래스로 구현되어있다.

    메인함수에서는 Shape를 자료형으로하는 포인터 배열을 선언하고 Circle클래스와 Rectangle를 동적할당 하였다.

    그리고 for문으로와서 Shape의 도형의 넓이를 출력하고 Rectangle일 경우에는 대각선의 길이까지 출력되게 하는 코드이다.

     

    		Rectangle* r = dynamic_cast<Rectangle*>(shapes[i]);
    		if (r != NULL) {
    			cout << "대각선의 길이 : " << r->GetDiag() << endl;
    		}
    	}

    그 중 이 코드의 의미는 shapes[i]를 동적캐스팅하겠다는 의미인데 <Rectangle*>는 shapes[i]의 자료형과 비교하여 캐스팅이 가능하면 r에 형변환을 시키고 만약 캐스팅이 불가능하면 NULL값을 반환한다.

    그래서 if문을 통해서 r이 NULL인지를 비교하고 대각선의 길이를 출력하는 코드이다.

     

    그런데 동적캐스팅은 성능적인 면에서 좋지 않기 때문에 일반적으로는 클래스를 설계할 때 동적캐스팅을 사용하지 않는 방향으로 설계하는 것이 좋다.

Designed by Tistory.