본문 바로가기

Information Technology/C++

[C++] 동적 형변환 Dynamic_casting

이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요 ;)


상속 구조를 통해 클래스를 이용하다 보면 부모 클래스의 포인터를 통해 자식 클래스의 인스턴스를 받기도 하고, 또 그 반대의 상황이 벌어질 때도 있습니다. 이번 포스팅에서는 그런 복잡한 상속 상황에서 사용하는 동적 형변환을 다뤄보겠습니다.

#include <iostream>
#include <string>
using namespace std;

class Base
{
public:
	int m_i = 0;
	virtual void print()
	{cout << "I'm Base" << endl;}
};

class Derived1 : public Base
{
public:
	int m_j = 1024;
	virtual void print() override
	{cout << "I'm D1" << endl;}
};

class Derived2 :public Base
{
public:
	string m_name = "Dr. Two";
	virtual void print() override
	{cout << "I'm D2" << endl; }
};

예제로 사용할 코드입니다. 부모 클래스인 Base 클래스를 정의하고, Base 클래스를 상속받는 Derived1 클래스와 Derived2 클래스를 정의했습니다.

int main()
{
	Derived1 d1;
	Base* base = &d1;
        base->print(); // Derieved1 클래스의 print 함수 호출
	cout << base->m_j << endl; // Derived 클래스의 멤버에 접근할 수 없기 때문에 error!
}

main 함수에서는 Derived 클래스의 인스턴스인 d1을 정의하고, Base 클래스의 포인터 변수 base에 d1의 주소값을 주었습니다.

Base 클래스의 print 함수는 virtual 함수이기 때문에 다형성을 통해 d1이 가지고 있던 Derived1 클래스의 print 함수가 base에게도 그대로 전달됩니다. 하지만 Base 클래스는 m_j라는 멤버를 가지고 있지 않기 때문에 d1이 가지고 있던 m_j에는 접근하지 못합니다. 이런 상황에서 불가피하게 base가 m_j에 접근해야 한다면 어떻게 해야 할까요? 이런 상황에서 사용하는 것이 바로 동적 형변환입니다. 

auto* base_to_d1 = dynamic_cast<Derived1*>(base);
// Dervied1* base_to_d1 = dynamic_cast<Derived1*>(base); // 위의 코드와 동일함

동적 형변환을 이용하는 방법은 위의 코드와 같습니다.

auto를 통해 새로운 포인터 변수의 자료형은 컴파일러에게 맡기고, dynamic_cast의 bracket <>으로 형변환의 대상이 되는 자료형을 입력하면 됩니다.

새로운 포인터 변수의 자료형을 직접 입력해도 상관 없는데, 꼭 형변환의 대상이 되는 자료형과 동일하게 입력해야 합니다.

auto* base_to_d1 = dynamic_cast<Derived2*>(base);
// 자신과 연결된 객체의 클래스가 아닌 다른 클래스의 객체로 동적 형변환을 하면 새로운 주소에 null 포인터를 저장

dynamic_cast는 런타임에서 형변환에 에러가 있는지 체크를 해주기 때문에,

위의 코드처럼 base Derived1 클래스가 아닌 다른 클래스로 동적 형변환을 실행하면 형변환이 이뤄지지 않고 새로운 주소에 그냥 null 포인터가 저장됩니다. 이점을 주의해야 합니다.

auto* base_to_d1 = static_cast<Derived2*>(base);
cout << base_to_d1->m_name << endl; // static_cast는 상속 관계에서는 최대한 형변환을 실행하기 때문에 error 없이 작동

dynamic_cast와 비슷하면서 다른 게 위의 코드에 나온 static_cast입니다. static_cast의 사용방법과 기능은 dynamic_cast와 비슷합니다.

static_cast는 상속 관계에서는 최대한 형변환을 시켜줍니다. 때문에 위의 코드에서 역시 base는 Derived1 클래스의 인스턴스를 Base 클래스 자료형의 포인터로 저장한 인스턴스이지만, Derived2 클래스가 Base 클래스와 상속 관계에 있기 때문에 형변환이 가능합니다. 

이는 런타임에서 에러를 확인하는 dynamic_cast와 달리, static_cast는 런타임에서 에러를 잡지 않고 컴파일 타임에서만 에러를 잡아주기 때문입니다.

그렇지만 static_cast 역시 아무 관계가 없는 클래스에 대해서는 형변환을 허용하지 않습니다.

dynamic_cast는 이미 정의된 주소를 사용하기 때문에 메모리 사용에 큰 문제가 없지만, static_cast는 새로운 클래스의 인스턴스를 복사하는 과정에서 메모리 사용에 문제가 발생할 수 있기 때문에 이점을 주의해서 사용해야 합니다.