[C++] Casting

seunghyun·2023년 7월 6일
0

업캐스팅, 다운캐스팅

  • 안전한 변환

    • 의미가 항상 100% 완전히 일치하는 경우
    • 같은 타입이면서 크기만 더 큰 바구니로 이동
    • 💡UPCASTING : 작은바구니에서 큰바구니로 이동 (방향성이 UP)
      • 파생 클래스의 객체를 기본 클래스의 포인터로 가리키는 것
      • 생물만 가리킬 수 있는 손가락 으로 사람을 가리킨다.
      • 마치 사람(파생) 을 생물(기본) 로 볼 수 있는 것처럼,
      • 파생 클래스의 객체를 기본 클래스의 객체처럼 다룰 수 있게 한다.
      • 이것은 파생 클래스 객체가 기본 클래스의 멤버를 포함하기 때문에 가능하다.
      • char->short, short->int, int->__int64
  • 아래 코드처럼, 업캐스팅한 기본 클래스의 포인터로는 기본 클래스의 멤버만 접근할 수 있다.

Point* pBase = pDer; // 업캐스팅
pBase->showColorPoint(); // ⛔️ 컴파일 오류 ⛔️ 기본클래스 Point 타입의 포인터 pBase로 파생 클래스인 ColorPoint 객체를 가리킨다. 하지만 pBase 는 Point 클래스의 포인터이므로, pBase 포인터로는 ColorPoint 객체 내 Point 클래스 멤버만 접근할 수 있다. showColorPoint() 함수는 Point 클래스의 멤버가 아니므로, 컴파일 오류가 발생한다.
  • 따라서, 업캐스팅 시 다음과 같이 명시적 타입 변환이 필요 없다.
Point* pBase = (Point*)pDer; // (Point*) 생략 가능

...파생 클래스의 포인터에 기본 클래스 타입의 주소가 치환되는 것을 다운 캐스팅이라고 한다.

  • 불안전한 변환
    • 의미가 항상 100% 완전히 일치한다고 보장하지 못하는 경우
    • 데이터 손실이 일어날 수 있으므로, 위험하다
    • 규모를 줄였으므로!
    • 🚫 complier error
    • 타입이 다르거나
    • 💡DOWNCASTING : 큰바구니에서 작은바구니로 이동 (방향성이 DOWN)
      • static_cast 가 이에 해당한다
      • 기본 클래스 포인터가 가리키는 객체를 파생 클래스의 포인터로 가리키는 것
      • UPCASTING 과 달리 명시적으로 타입 변환을 지정해야 한다.
int main(){
	ColorPoint cp;
	ColorPoint *pDer;
	Point* pBase = &cp; // 💡업캐스팅

	pBase->set(3,4);
	pBase->showPoint();

	pDer = (ColorPoint *)pBase; // 💡다운캐스팅
	pDer->setColor("Red"); // 정상 컴파일
	pDer->showColorPoint(); // 정상 컴파일
}

C++ 캐스팅

C++ 의 캐스팅에 대해 본격적으로 알아보자

1) static_cast ⭐️
2) dynamic_cast ⭐️
3) const_cast
4) reinterpret_cast

이러한 형변환 연산자들은 각각 다른 상황에서 사용되므로 올바른 형변환 연산자를 선택하여 코드를 작성해야 한다. 주의할 점은 reinterpret_castconst_cast는 잘못된 형변환을 수행할 수 있으므로 사용할 때 주의해야 한다.

static_cast

타입 원칙에 비춰볼 때 상식적인 캐스팅만 허용해 준다.

  • float ratio = static_cast<float>(hp) / maxHp;

그러나 '상식적인' 이라는 의미가 항상 '옳다' 는 것은 아니다

Knight* k = new Knight();
Player* p = k; // 이건 '나이트 is a 플레이어' 너무 당연해서 언어 차원에서 암시적으로 허용

Knight* k2 = static_cast<Knight*>(p); // 원복시키고 싶을 때, 언어를 안심시켜준다
ItemType itemType = item->GetItemType();
if (itemType == IT_Weapon)
{
	Weapon* weapon = static_cast<Weapon*>(item);
}

dynamic_cast

상속 관계에서의 안전 변환이다.
항상 되는 것은 아니며, 다형성 코드가 있어야 한다.

즉 하나라도 virtual 함수가 있어야 활용이 가능하다. (대표적으로 소멸자를 virtual 로 만드는 것)

왜 그럴까? 다른 캐스팅과 달리 RTTI(RunTime Type Information) 이기 때문이다.
가상함수를 하나라도 만들면 가상함수 테이블을 가리키는 공간이 생기고
가상함수테이블에는 함수주소들이 있고 이런게 동적 바인딩의 원리인데
이렇게 원본 타입을 확인해서 되는 경우에만 캐스팅을 해주고 그게 아니면 0nullptr 로 밀어버린다.

downcasting과 같은 동적 형변환을 수행한다.
런타임에 타입을 확인하고 안전한 형변환이 아니면 nullptr 또는 예외를 반환한다.

class Base { virtual void foo() {} };
class Derived : public Base {};

Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // Base 포인터를 Derived 포인터로 형변환
Archer* k = new Archer();
Player* p = k;

Knight* k2 = dynamic_cast<Knight*>(p); // k2 에는 nullptr 가 들어있다

const_cast

상수성(constness)을 제거하거나 추가한다.
주로 포인터나 참조를 통해 사용되며, 상수성을 변경하여 값을 수정하거나 상수 객체를 비상수 객체로 형변환한다.

const int a = 10;
int* b = const_cast<int*>(&a); // 상수성을 제거하여 값을 수정할 수 있도록 함
const char* name = "SH";
char* name2 = const_cast<char*>(name);

reinterpret_cast

포인터, 참조, 정수 간의 임의의 형변환을 수행한다.
안전하지 않은 형변환으로, 주로 저수준의 형식 변환에 사용된다고 한다.

int a = 10;
int* b = reinterpret_cast<int*>(a); // 정수를 포인터로 형변환
Dog* dog = reinterpret_cast<Dog*>(k);

0개의 댓글