업데이트:

태그: ,

카테고리:



REF : 윤성우의 열혈 C++

C++의 형변환 연산

  • C++에선 기존 C스타일의 형변환을 Old C-style 이라고 부른다, 단순히 호환성을 위해 존재한다.
  • 기존 C스타일의 형변환은 형변환의 목적을 한 눈에 알아볼 수 없다.는 문제점이 존재한다.
  • 코드를 읽는사람으로 하여금 해당 형변환이 어떤 의도로 된것인지 쉽게 예측할 수 없기때문에 그렇다.
  • 이러한 논란과 문제점으로 인해 C++만의 형변환 연산자가 추가되었다.
  • C++만의 형변환 연산자와 규칙을 알아보자.

1. dynamic_cast

  • 상속관계에서의 안전한 형 변환을 지원한다.
  • 유도클래스의 포인터와 참조형 데이터기초 클래스의 포인터 및 참조형 데이터로 형변환하는 경우(업캐스팅)
    #include <iostream>
    using namespace std;
    
    class Base
    {
      public:
        void VFnc(void)
        {
          cout<<"This is Base"<<endl;
        }
    };
    
    class Derived : public Base
    {
      public:
        void VFnc(void)
        {
          cout<<"This is Derived!"<<endl;
        }
    };
    
    int main(void)
    {
      Derived *ptr2 = new Derived;
    
      /*dynamic_cast upcasting*/
      Base *base1 = dynamic_cast<Base *>(ptr2);
      base1->VFnc(); //"This is Base"
    }
    
    • Dynamic Cast로 업캐스팅을 하게되면, 기초클래스의 메서드를 호출하게된다.
    • 물론, 이때도 VFnc가 virtual로 선언되어있다면, 포인터형을보고 호출하는게 아닌, 내부 객체의 가상함수테이블에서 함수를 호출하므로, 유도클래스의 함수를 호출하게된다.
  • 기초 클래스가 Polymorphic 클래스(가상함수가 있는 클래스)일때, 유도클래스의 포인터 및 참조형 데이터로 변환(다운캐스팅)
    class Base
    {
      public:
        virtual void VFnc(void)
        {
          cout<<"This is Base"<<endl;
        }
    };
    
    class Derived : public Base
    {
      public:
        void VFnc(void)
        {
          cout<<"This is Derived!"<<endl;
        }
    };
    
    int main(void)
    {
      Base *ptr1 = new Derived;
        
      /*dynamic_cast downcasting*/
      Derived *derive1 = dynamic_cast<Derived *>(ptr1);
    }
    
    • Base클래스가 가상함수를 지닌 Polymorphic 클래스이므로, Derived 클래스도 Polymorphic이다.
    • 이런 Polymorphic 클래스를 업캐스팅하게되면 유도클래스의 포인터로 형변환할 수 있다.


2. static_cast

  • dynamic_cast보다 많은 형 변환을 허용한다.
  • 다운캐스팅, 업캐스팅을 자유롭게 사용할 수 있다.
  • 다양한 형태의 형변환을 허용하는데, 안정성을 위해서는 dynamic_cast를 사용하고, 제한적인 상황에서만 static_cast를 사용해야한다.
#include <iostream>
using namespace std;

class Base
{
	public:
		void VFnc(void)
		{
			cout<<"This is Base"<<endl;
		}
};

class Derived : public Base
{
	public:
		void VFnc(void)
		{
			cout<<"This is Derived!"<<endl;
		}
};

int main(void)
{
	Base *base1 = new Derived;
	Derived *derive = static_cast<Derived *>(base1);
	derive->VFnc();

	Base *base2 = new Base;
	Derived *error = static_cast<Derived *>(base2);
	error->VFnc(); //"This is Derived!"
}
  • 실제 내부 객체가 Derived인 포인터를 Derived로 형변환하는 것은 논리적으로 정당화될 수 있다.
  • 하지만, 내부 객체가 Base인 포인터를 Derived로 바꾸는 것은 정당화될 수 없다.
  • 실제로 현재 error라는 포인터는 static_cast로는 컴파일 에러를 발생시키지 않는다.
  • 가상함수 테이블이 없으므로, 포인터형만으로 함수를 호출하게되는데, Derived 포인터이므로, Derived클래스의 VFnc를 호출하게된다.
    • 실제 내부 객체가 base임에도 불구하고, 단순히 포인터형에 의해 이를 호출하는 것은 논리적으로 말이 안된다.
  • 하지만, dynamic cast로 형변환을 하게되면 현재 Base가 Polymorphic하지 않으므로, 컴파일에러를 발생시킨다.
  • 이를 막기위해 Base클래스의 메서드 중 하나를 virtual로 선언해주면 되긴하나, dynamic_cast 연산자는 형변환하고자하는 대상 객체가 아닐경우, NULL을 반환한다.
    • 만약 참조형일 경우, bad_cast exception이 발생한다.
    #include <iostream>
    using namespace std;
    
    class Base
    {
      public:
        virtual void VFnc(void)
        {
          cout<<"This is Base"<<endl;
        }
    };
    
    class Derived : public Base
    {
      public:
        void VFnc(void)
        {
          cout<<"This is Derived!"<<endl;
        }
    };
    
    int main(void)
    {
      Base *base2 = new Base;
      Derived *error = dynamic_cast<Derived *>(base2); //Null 반환
      if (!error)
        return 0;
    }
    


    • dynamic_cast는 컴파일시간이 아닌, 프로그램이 실행될때의 안정성을 검사하기때문에, static_cast보다 실행속도는 느리지만 안정적인 형변환이 가능하다.
    • 이러한 기능을 RTTI라고하며, dynamic_cast와 RTTI를 참고하자
    • 이렇듯, static_cast보다 dynamic_cast가 보다 안전한 형변환을 할 수 있으므로, 제한적이 상황에서만 static_cast를 사용해야한다.



  • 다만, static_cast가 유용하게 사용되는 경우가 있는데, 기본 자료형 간의 형 변환이다.
  • int, float, double 등 데이터의 형변환을 할 때 사용되는데, static_cast가 C의 형변환과 다른 점은
  • 포인터간의 형변환을 허용하지 않는다.는 점이다.
#include <iostream>

int main(void)
{

	int num = 0;

	int *p1 = &num;
	
	float *e1 = static_cast<float *>(p1);//컴파일 에러
	float *e2 = (float *)p1;
	return 0;
}
  • 실제로, 이 코드를 실행해보면, 포인터를 다른 포인터로 형변환하는 행위는 불가능하다.
  • 이렇게 static_cast가 기본 자료형간 형변환, 상속관계의 클래스와 포인터의 형변환만을 허용하므로, 프로그래머는 이 둘 중 하나를 파악할 수 있다.



3. const_cast

  • const 성향을 삭제하는 캐스팅
  • const의 성향을 삭제하는 것이 const의 가치를 떨어뜨린다고 생각할수도 있지만, 나름의 이유가 있다.
    • 함수의 인자 전달 시, const 선언으로 인한 형불일치를 수정해준다.
    • 라이브러리 함수를 사용할때, const형이 맞지 않을 때 제한적으로 사용하는 것이 좋다.



4. reinterpret_cast

  • 상관없는 자료형으로의 형변환
#include <iostream>
using namespace std;

int main(void)
{
	int num = 1;

	char *ptr = reinterpret_cast<char *>(&num);

	for (int i = 0; i < sizeof(num); i++)
	{
		cout<<static_cast<int>(*(ptr + i))<<endl;
	}
	return 0;
}
  • 정수자료형 int가 4바이트로 숫자를 표현한다.
  • 이를 1바이트단위로 접근할 수 있도록 char *로 변환해서 1바이트씩 값을 읽어온다.
  • 이러한 reinterpret_cast의 사용은 주소값 출력에도 이용될 수 있다.
  • C에서는 포인터의 주소를 출력하기위해서는 포인터의 형을 long long으로 형변환했어야했는데, reinterpret캐스팅로 이것이 가능하다.

댓글남기기