Casting

static_cast

  • 논리 형변환
  • 주로 Scalar Type들 간의 캐스팅
    • 형 변환 함수가 정의되어 있으면 이 함수를 호출하면서 실행
    • 연산자 오버로딩을 통하여 변환 가능한 자료형을 추가할 수 있다
  • 컴파일 타임에서 캐스팅
// 커스텀 클래스들 선언
class MyFloat
{
public:
	float Real;
};

class MyInt
{
public:
	int Num;

  // 형 변환 함수 
	operator MyFloat(void)
	{ 
		MyFloat f;
		f.Real = static_cast<float>(Num);
		return f;
	};
};

int main()
{
	MyInt i = { 5 };
	MyFloat f = { 0.f };

  // 정상적으로 변환 수행
  // 형 변환 함수가 있기 때문
	f = static_cast<MyFloat>(i);
	return 0;
}

reinterpret_cast

  • 강제 형변환
  • Pointer Casting
    • 포인터와 포인터, 또는 포인터와 일반 자료 끼리의 형 변환으로 주로 사용
  • static_cast는 하나의 함수 호출이지만, reinterpret_cast는 해당 자료형을 다음 자료형으로 인식하라라는 메세지를 컴파일러에게 전달하는 역할
// 아래 코드의 결과는
int i = 5;
float *pF = new float;

// memcpy는 메모리를 일방적으로 복사하는 함수
// 두 번째 인수에서 첫번째 인수로 사이즈(세번째 인수) 만큼 복사 진행
memcpy(pf, &i, sizeof(float) < sizeof(int) ? sizeof(float) : sizeof(int));

cout << *pf;

delete pf;

// 이 코드와 같다
int i = 5;
float pf = *(reinterpret_cast<float *>(&i));

cout << pf;

// 올바른 결과를 출력하지는 못한다

const_cast

  • non const -> const
    • 그냥 const 키워드를 써라
  • const -> non const
    • 잠재 위험이 너무 많다
  • 포인터 형 뿐만 아니라 레퍼런스 형에도 사용가능하다
// const_cast
#include <iostream>
using namespace std;

void print (char * str)
{
  cout << str << '\n';
}

// 아래 print 함수는 정상적으로 작동한다 
// constless object를 read만 하고, write을 하지 않기 땨문
// constless object에 write하는 건 undefined behavior를 유발
// c가 const 이기 대문
int main () {
  const char * c = "sample text";
  print ( const_cast<char *> (c) );
  return 0;
}

Usage

// 어떤 클래스에서 string의 index에 해당하는 char을 반환하는 함수가 있다고 하자
// 두 함수의 코드 중복을 줄이기 위해 비상수 함수에서 상수 함수를 호출하는 방법을 사용한다
class TextBlock : 
{
  private:
  string text;
  
  public:
  
  // 상수 반환 함수
  const char& operator[](int position) const
  {
    ...
    return text[position];
  }
  
  // 비상수 반환 함수
  char& operator[](int position)
  {
    ...
    // return text[position]
    return const_cast<char&>(                        // 2. const char&로 반환된 값을 char&로 변환
      static_cast<const TextBlock&>(*this)[position] // 1. *this 타입을 const로 변환
    );
  }
}
  • static_cast를 사용한 이유는 비상수 함수에서 상수 함수를 호출하기 위함이다
    • 이 과정을 거치지 않으면 비상수 함수는 재귀적으로 자기 스스로를 호출하게 될 것
  • 상수 함수를 호출함으로써 추가된 const 키워드를 제거하기 위해 다시 const_cast를 사용
    • 멤버 변수 text가 원래 const가 아니므로, 비상수 메서드를 통해 read/wrtie이 모두 가능
  int i = 3;                      // i는 비상수
  const int& rci = i;             // rci는 상수 레퍼런스
  const_cast<int&>(rci) = 4;      // OK: modifies i (i가 비상수이기 때문에 정상 시행)

  const int j = 3;                // j는 상수
  int* pj = const_cast<int*>(&j); // 비상수로 변환 시도
  *pj = 4;                        // 원본이 상수인 상태에 write를 시도하므로, undefined behaviour 발생

dynamic_cast

  • Inheritance Casting: Base <-> Derived
  • 런타임에서 캐스팅
  • 다형 형식의 파생 클래스 간의 캐스팅
    • virtual method로 다형 형식의 계층이어야만 RTTI를 사용해 캐스팅 가능
  • 캐스팅 실패 시 null을 리턴
    • null 체크를 통해 안전성 확보 가능
  • upcasting
    • 파생 클래스 -> 부모 클래스로 캐스팅
  • downcasting
    • 부모 클래스 -> 파생 클래스로 캐스팅
// ex1
Parent* parent = new Parent; // 페런트 그 자체
Child* child = new Child; // 차일드 그 자체
Parent* parent2 = new Child; // 페런트의 탈을 쓴 차일드

Parent* upcasted = dynamic_cast<Parent*>(child);
Child* downcasted1 = dynamic_cast<Child*>(parent2);

// 캐스팅 실패 시 null 을 반환,
// 안전한 캐스팅 가능
Child* downcasted1 = dynamic_cast<Child*>(parent); // failed

// ex2
class A {};
class B : public A {};

int main()
{
  A* b = new B;
  // 아래 코드는 컴파일 불가
	B* a = dynamic_cast<B*>(b);

  return 0;
}

// 클래스 A와 B가 다형형식을 공유하지 않기 때문
// 아래와 같이만 변형해도 컴파일 가능
class A {
  virtual ~A();
};
class B : public A {
  ~B();
};

How dynamic_cast works?

  • polymorphic type에만 사용 가능한 캐스팅
    • virtual method를 가지고 있거나,
    • virtual destructor를 가지고 있거나,
    • virtual base class를 가지고 있는 클래스는 polymorphic
  • polymorphic 타입은 Virtual Method Table(VMT)를 데이터 레이아웃에 가진다
    • polymorphic 타입이 아니면 VMT가 없다 (에러 발생)
    • VMT에서 변환 가능한 타입을 찾아 캐스팅

다른 캐스팅 방법

// basically the same behavior
int(a); // Function
(int)a; // Operator

캐스팅 시도 순서*

  1. const_cast
  2. static_cast
  3. static_cast + const_cast
  4. reinterpret_cast
  5. reinterpret_cast + const_cast

출처

Categories: ,

Updated: