캐스팅 4총사 (static, dynamic, const, reinterpret cast)

 

인프런 Rookiss님의 'Part1: C++ 프로그래밍 입문' 강의를 기반으로 정리한 필기입니다. 
😎[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 강의 들으러 가기!

 

 


 

cast의 종류 :  static cast,  dynamic cast,  const cast,  reinterpret cast

 

static cast

-  static cast는 컴파일 타임에 진행된다.

-  타입 변환에 자주 사용. 다운 캐스팅, 업 캐스팅 시 사용 가능.

-  Cast 시 안전 검사를 하지 않는다.

 

dynamic cast

-  RTTI를 기반으로 런타임에 적용한다.

-  cast로 클래스 간 Up, Down cast에 자주 사용되며 cast 시 안전 검사를 한다.

 

const cast

-  변수에 const 속성을 등록 및 해제하기 위해 사용하는 cast다. (= const를 붙이거나 떼거나 할 때 사용한다.)

 

reinterpret cast

-  포인터 간 변환을 위해 사용하는 cast다.

-  임의의 포인터 객체를 다른 포인터 객체로 변환할 수 있다. 이러한 변환은 안전하지 않을 수 있기 때문에 주의가 필요하다.

 

 

※  C++에서는 가상 함수(virtual function)을 사용하여 런타임 다형성을 구현할 수 있으므로 업캐스팅(Up Casting) 없이도 객체 타입에 따라 올바른 함수가 호출될 수 있다. 따라서 cast를 명시적으로 사용하는것 보다 가상 함수를 사용하는 것이 더 좋다. 


 

1. static _cast

 

static_cast : 타입 원칙에 비춰볼 때 상식적인 캐스팅만 허용해준다. 4가지 cast 방식 중 가장 많이 활용한다.
 

static_cast 사용 경우: 1. 타입 변환, 2. 다운캐스팅(Down Casting), 3. 업캐스팅(Up Casting)

 

1) 타입 변환

  • int <-> float

 

2) 다운캐스팅(Down Casting)

  • 다운캐스팅은 '부모클래스->자식클래스',  단, 안전성 보장 못함
  • Player* -> Knight* 

 

3) 업캐스팅(Up Casting)

  • 잘 사용하지 않음.
  • '자식->부모' 이기 때문에 굳이 캐스팅을 하지 않아도 문제가 되지 않는 경우가 대부분이다.

 

더보기
#include <iostream>
using namespace std;
 
class Player { };

class Knight : public Player { };

class Archer : public Player { };

int main()
{
	int hp = 100;
    int maxHp = 200;
    // float ratio = hp / maxHp               // 인수 나눗셈이어서 0.5가 아닌 0으로 출력된다
    // float ratio = (float)hp / maxHp;       // float에서 int를 나누면 float가 우선순위가 높아서 실수로 나온다
    float ratio = static_cast<float>(hp) / maxHp;   // 다운캐스팅. C++ 친화적인 문법

    // 자식->부모 : 굳이 캐스팅이 필요가 없다
    Knight* k = new Knight();
    Player* p2 = k;    // 캐스팅을 하지 않아도 문제가 되지 않는다

    // 부모->자식 
    Player* p = new Knight();
    // Knight* k1 = (Knight*)p;  // (Knight*)p는 c스타일 타입변환
    Knight* k1 = static_cast<Knight*>(p);  // 좀 더 정확한 의미
    
    // 부모->자식, 다운캐스팅이 문제되는 상황
    Player* p = new Archer();
    Knight* k1 = static_cast<Knight*>(p);
    k1-> //포인터로 접근해서 값을 고치면 엉뚱한 메모리를 고치는 상황이 발생할 수 있다.

    return 0;
}

 

 


 

2. dynamic_cast

 

dynamic_cast : 상속 관계에서의 안전 형변환

 

RTTI (RunTime Type Information)

  • 다형성을 활용하는 방식
  • 런타임 중 타입을 확인할 수 있다.

 

dynamic_cast를 사용하려면 virtual 함수가 필요하다.

  • virtual 함수를 하나라도 만들면, 객체의 메모리가상 함수 테이블 (vftable) 주소가 기입된다.
  • 만약 잘못된 타입으로 캐스팅을 했으면, nullptr 반환한다. dynamic_cast의 경우 RTTI의 가상 함수 테이블(vftable)를 활용하여 자신이 어떤 애인지 확인하기 때문에 잘못된 타입으로 변환하면, nullptr로 반환한다. (반면에 static_cast는 메모리오염이 발생한다.)
  • 이를 이용해서 맞는 타입으로 캐스팅을 했는지 확인을 유용하다.
  • static_cast보다 느리다.

 

↑ virtual 함수가 하나도 없으면 위의 이미지와 같은 에러 문구를 띄우며 빌드가 되지 않는다.  

더보기
#include <iostream>
using namespace std;

class Player
{
public:
    virtual ~Player() { }
};

class Knight : public Player { };

class Archer : public Player { };

class Dog { };

int main()
{
	Player* p = new Knight();
    Knight* k2 = dynamic_cast<Knight*>(p);

    return 0;
}

 

nullptr 반환 예시 코드

#include <iostream>
using namespace std;

class Player
{
public:
    virtual ~Player() { }
};

class Knight : public Player { };

class Archer : public Player { };

class Dog { };

int main()
{
	Player* p = new Archer();
    Knight* k2 = dynamic_cast<Knight*>(p); // 잘못된 타입으로 캐스팅한 경우. nullptr 반환

    return 0;
}
nullptr 반환

 


 

3. const_cast

 

const_cast : const를 붙이거나 떼거나 할 때 사용한다.

 

사용 예시: 공동 작업을 할 때 누군가 const로 값 변화를 막아두었을때, 값을 변화시켜 테스팅하고 싶을 때 사용한다.

 

더보기
#include <iostream>
using namespace std;

void PrintName(char* str)
{
    cout << str << endl;
}

int main()
{
	// 공동 작업 시 PrintName 함수를 수정하면 안 되는 상황
	PrintName("Rookiss"); // const char* 형이기 때문에 통과 안됨
    PrintName((char*)"Rookiss") // c스타일의 타입변환. 통과는 되지만 추천하지 않는 방식
    PrintName(const_cast<char*>("Say my name")); // c++ 스타일. const_cast

    return 0;
}

 

" "는 const char* 형식이기 때문에 위의 void PrintName(char* str)에 통과되지 않는다.

 

 

4. reinterpret_cast

 

reinterpret_cast는 포인터랑 전혀 관계없는 다른 타입 변환 등에 사용한다.

 

  • 가장 위험하고 강력한 형태의 캐스팅
  • 're-interpret' : 다시-간주하다/생각하다.
  • 사용 예시: 
    • void* p = malloc(1000);
    • 해당 경우에 malloc은 void*로 반환한다.
    • Dog* dog2 = reinterpret_cast<Dog*>(p);로 써서 void*형을 사용하여 Dog로 만들어주겠다.

 

더보기
#include <iostream>
using namespace std;

class Player
{
public:
    virtual ~Player() { }
};

class Knight : public Player { };

class Archer : public Player { };

class Dog { };

int main()
{
	Knight* k2 = dynamic_cast<Knight*>(p);
    
    __int64 address = (__int64)(k2); 				 // c스타일 캐스팅
    __int64 address = reinterpret_cast<__int64>(k2); // c++ reinterpret_cast

    Dog* dog1 = reinterpret_cast<Dog*>(k2); // Knight와 Dog는 상관관계가 없지만 reinterpret_cast 할 수 있다.  

    void* p = malloc(1000); // void*형으로 1000byte를 할당하겠다.
    // 할당한 메모리를 Dog* 형으로 사용하겠다.
    Dog* dog2 = p;        	// 통과 안됨
    Dog* dog2 = (Dog*)p;  	// c스타일 캐스팅. (Dog*)는 사용하는 경우가 많아 실수의 여지가 높다.
    Dog* dog2 = reinterpret_cast<Dog*>(p); 
    
    return 0;
}

 

 

 

 

언리얼 cast

 

'⭐ Programming > C++' 카테고리의 다른 글

[C++] 함수 포인터 1  (0) 2022.04.15
[C++] 전방선언  (0) 2022.04.12
[C++] 얕은 복사 vs 깊은 복사 2  (0) 2022.04.10
[C++] 얕은 복사 vs 깊은 복사 1  (0) 2022.04.09
[C++] 타입 변환 4: 포인터  (0) 2022.04.09