[C++] C++의 캐스팅(static_cast, const_cast, dynamic_cast, reinterpret_cast)

Donghee·2025년 3월 24일

개요

C언어 형식의 명시적 캐스팅은 다음과 같다.

float f = 2.5f;
int i = (int)f;

(캐스팅 대상 자료형)캐스팅할 객체의 형식으로 이루어진다.
꽤 직관적이라 편하지만, 후술할 여러 위험성이 존재하고, 어떤 목적으로 사용한 것인지 불명확하다. 따라서 C++에서는 캐스팅을 여러 형태로 분리하고 있다. C 형식 캐스팅에 비해서 어떤 장점이 있는지, 어떤 형태들로 분리되는지 알아보자.

C++ 명시적 캐스팅

C++에는 static_cast, const_cast, dynamic_cast, reinterpret_cast 4개의 캐스팅이 존재한다.

static_cast

static_cast<type-id>(표현식)
static_cast표현식의 타입만을 기준으로 표현식type-id의 유형으로 변환해준다.
'static'이라는 이름에서 볼 수 있듯이, 런타임에 표현식의 자료형을 동적으로 확인하는 것이 아니라, 컴파일 타임에 정적으로 표현식의 자료형을 그 자체로만 평가한다.

따라서 static_cast는 기본 타입끼리의 암시적 형변환을 명시할 때, 또는 업캐스팅에 주로 사용된다.
기본 클래스 포인터를 파생 클래스 포인터로 캐스팅되는 다운캐스팅으로도 사용할 수는 있다.
하지만 런타임에서 올바른 변환인지 확인하지 않기 때문에, 잘못된 변환일 때에도 그 사실을 모르고 무작정 형 변환을 진행한다.

따라서 static_cast에서 다운캐스팅을 진행한다면, 변환하려는 파생 클래스 포인터 타입이 명확할 때만 사용하여야 한다. 명확하지 않거나 한번 확인이 필요하다면, 후술할 dynamic_cast를 활용하자.

또한, static_cast로는 const 객체에 대한 포인터에서 const 속성을 제거할 수 없다. 이 또한 후술할 const_cast를 활용해야 한다.
(포인터가 아닌 일반 const 객체를 그냥 객체로 형변환하는 것은 복사하는 행위이므로 가능하다는 것에 유의)

  • 사용 예시
class Base {};
class Derived : public Base {};

void f(Base* pb, Derived* pd) {
   Derived* pd2 = static_cast<Derived*>(pb);   // 다운캐스팅, 위험
   											   // pb가 Derived로 동적 할당된 객체가 아니라면 잘못된 변환
   Base* pb2 = static_cast<Base*>(pd);   // 업캐스팅, 안전한 변환
}

int i = 65;
char c = static_cast<char>(i); // 기본 타입끼리의 변환 (암시적 형변환)

const int* cp = &i; // const 객체에 대한 포인터
// int* p = static_cast<int*>(cp); // 변환 불가, 에러!

const_cast

const_cast<type-id>(표현식)
위에서 언급했듯이, const_cast는 표현식의 const 속성을 제거한다. 보통은 const 객체에 대한 참조자, 혹은 const 객체에 대한 포인터를 표현식으로 자주 사용한다. 이는 일반 const 객체는 표현식에 넣어봤자 복사값으로만 활용되어 const속성을 제거하는 의미가 따로 없기 때문이다.

  • 사용 예시
class Base
{
private:
    int i;
    
public:
    Base(): i(3) {}
    void Set_i(int new_i) const
    {
        // this->i = new_i; // const 함수에서 멤버 변수를 변경하므로 오류
        const_cast<Base*>(this)->i = new_i; // 이 함수를 호출하는 객체가 const여도,
        									// const_cast를 통해 변환하므로 멤버 변수 변경 가능
    }
};

dynamic_cast

dynamic_cast<type-id>(표현식)
dynamic_cast는 상속 계층 구조를 따라 포인터/참조자인 표현식을 type-id의 유형에 따라, 캐스팅해준다. 위에 서술했듯이, static_cast와 다르게 동적으로(dynamic) 표현식이 가리키는 객체를 확인한다. 만약 그 객체가 type-id로 형 변환이 불가하다면, 포인터일 경우 nullptr가 저장이 되고, 참조자일 경우 예외를 발생시킨다. 따라서 프로그래머가 처리를 하면, 런타임 중에 확인이 가능하다.

그러나 dynamic_cast는 다형성을 띄는 객체끼리만 변환이 가능하다. 여기서 '다형성을 띄는 객체'란, 그 객체의 타입에 가상 함수가 하나라도 포함되어 있는 객체를 뜻한다. 또한 컴파일 타임에 타입 정보를 지정하는 static_cast와 다르게 'RTTI'를 통해 런타임에 비용을 더 소모한다.

  • 사용 예시
class Base 
{
	virtual void f() {} // 다형성을 가져야 dynamic_cast 변환 가능
};
class Derived : public Base {};
class Derived2 : public Base {};


int main()
{
  Base* b = new Derived();
  Derived2* d2 = dynamic_cast<Derived2*>(b); // b에는 Derived 객체에 들어있기 때문에, 다운캐스팅 불가능

  if(d2 == nullptr) { cout << "nullptr"; } // nullptr이 출력된다.
}

reinterpret_cast

reinterpret_cast<type-id>(표현식)
reinterpret_cast는 모든 포인터를 다른 포인터 유형으로 변환시켜주는 캐스팅 연산자다. 표현식에 해당하는 객체의 비트를 재해석해 type-id에 해당하는 포인터 유형으로 변환한다. 또한 정수형 -> 포인터, 포인터 -> 정수형의 변환도 지원한다.

reinterpret_cast는 컴파일러에게 "표현식을 'type-id'로 취급해줘'라고 부탁할 뿐, 그 이상의 작업은 일어나지 않는다. (정수형 <-> 포인터 변환 제외) 실제 메모리 구조를 뒤엎는 것이 아니라는 것이다. 따라서 잘못 사용해 올바른 변환이 일어나지 않는 경우에는, 즉시 오류가 발생한다. 들어온 네트워크의 바이트를 패킷 클래스로 변경하는 등의 저수준 단계에서만 활용이 된다.

struct CharacterStat
{
	...
	float MaxHp;
	float Attack;
	float AttackRange;
	float AttackSpeed;
	float MovementSpeed;

	CharacterStat operator+(const CharacterStat& Other) const
	{
		const float* const ThisPtr = reinterpret_cast<const float* const>(this); // 본인 객체를 const float 포인터로 변환
		const float* const OtherPtr = reinterpret_cast<const float* const>(&Other); // Other를 const float 포인터로 변환

		CharacterStat Result;
		float* ResultPtr = reinterpret_cast<float*>(&Result);
		int32 StatNum = sizeof(CharacterStat) / sizeof(float); // float 포인터를 배열 취급할 때 요소가 몇개 나올지 계산
		for (int32 i = 0; i < StatNum; i++)
		{
			ResultPtr[i] = ThisPtr[i] + OtherPtr[i]; // const float 포인터로 변환한 것들을 배열 취급해 연산
		}

		return Result;
	}
};

만약 CharacterStat이 float만 이루어지지 않고, MaxHp 혼자만 int였다면, operator+를 사용할 때 미정의 행동이 발생할 것이다. CharacterStat의 멤버변수가 float뿐이기 때문에, reinterpret_cast를 통한 포인터 변환이 가능했다.

C++에서의 C언어 형식의 명시적 캐스팅

C++에서 위에 서술한 C언어 형식의 명시적 캐스팅을 진행하면, 컴파일러에서는 C++의 4가지 캐스팅 중 어떤 캐스팅을 이용할지 체크한다. 결국 C++ 캐스팅으로 변환이 되는 것이다. 캐스팅 목적도 명확하지 않고, 강제 변환으로 인해 타입 안전성도 떨어뜨려 C++에서는 추천되지 않는다. 가급적이면 C++의 캐스팅 연산자들을 이용하자.

참고

profile
마포고개발짱

0개의 댓글