C++에는 대표적으로 4개 정도의 Cast 문법이 존재한다.

Static Cast (static_cast)

/* 사용 예 */
#include <iostream>

int main()
{
	int i = 4;
	double d = 8.8;
        /* static_cast<double>(i)를 통해 i를 double로 형변환 시킨것을 받음 */
	auto a = static_cast<double>(i);
	std::cout << typeid(a).name() << std::endl;
}
  • 형변환 능력을 가진 가장 기본적인 캐스트 연산자이다.
  • 이미 캐스트 할 자료가 어떤 타입인지 어느정도 알 수 있을 경우 사용하는게 옳다.
  • C 와는 다르게 형변환에 대한 타입체크를 Run Time에서 하지않고, Compile 타임에서 정적으로 실행하기때문에 대부분의 경우 미리 오류를 발견할 수 있다.
  • 제약사항은 C 스타일과 비슷하게 struct 타입을 int나 double 타입으로 형변환 할 수 없다는 것이나, float 타입을 포인터 타입으로 변환할 수 없는 등의 제약이 있다.
  • 위의 제약은 메모리에 치명적인 경우가 대부분이라 필요한 제약이며, C스타일의 경우 체크가 느슨해서 문제를 발견하기 어렵다.
  • 포인터 형변환이 안된다. 자료형 자체가 다르니 되도 의미는 없다. C 에서는 컴파일러가 캐스팅을 거의 신경쓰지 않기 때문에 가능하긴 하다.
  • 포인터 단에서 형변환이 의미있는 경우는 상속받은 객체에 대한 업캐스팅, 다운캐스팅의 경우다.
  • 키워드 형태(static_cast)로 노출되어 있어 찾기가 편하다.
  • 아래 설명할 Dynamic Cast와는 캐스팅 판별 시간이 정반대이다.

암시적 캐스팅 / 명시적 캐스팅

static_cast는 C Style 캐스팅의 문제를 고치기 위해 키워드까지 생성하며 탄생했다.

C++은 컴파일러가 캐스팅을 확인하며, 에러가 나도 컴파일러단에서 미리 문제를 알려주기 때문에 추후 발생할 문제를 어느정도 막을 수 있다.

#include <iostream>

int main()
{
	double d = 8.8;
	/* C Style의 명시적 캐스팅(Explicit Cast) */
	int ce = (float)d;
	/* 암묵적 캐스팅(Implicit Cast)*/
	int ci = d;
	/* C++ Style의 정적 캐스팅(Static Cast) */
	int cpp = static_cast<int>(d);

	std::cout << d << " " << ce << " " << ci << " " << cpp << std::endl;
}

다운캐스팅 / 업캐스팅

다운캐스팅 : 부모를 자식에게 할당하는 것

업캐스팅 : 자식을 부모에게 할당하는 것

#include <iostream>

class Parent {};
class Baby : public Parent {};

int main()
{
	Parent parent, *p_parent;
	Baby baby, *p_baby;
	// 업 캐스팅
	p_parent = static_cast<Parent*>(&baby);
	// 다운 캐스팅
	p_baby = static_cast<Parent*>(&baby); //애초에 IDE에서 빨간줄이 뜬다.
	return 0;
}

업캐스팅의 경우 자식은 구체화된 객체를 부모인 추상적인 객체에 할당하니 별로 문제되지 않는다. 오히려 자식들의 다형성을 보편화시키면서 다룰 수 있다는 장점도 있다.

다운캐스팅의 경우에는 부모를 자식에게 할당하는 경우에 부모클레스에 없는 자식클래스에 존재하는 멤버들에 접근하는 본래에 있지도 않았던 값을 참조하거나 사용하려할 수 있다는 위험이 존재한다.

그러한 위험을 조기에 차단하기 위해 dynamic_cast 가 나왔다.

Dynamic Cast (dynamic_cast)

#include <iostream>

class Parent {};
class Baby : public Parent {};

int main()
{
	Parent parent, *p_parent;
	Baby baby, *p_baby;
	// 업 캐스팅
	p_parent = static_cast<Parent*>(&baby);
	// 다운 캐스팅
	p_baby = static_cast<Parent*>(&baby); //애초에 IDE에서 빨간줄이 뜬다.
	return 0;
}
  • 런타임에 동적으로 상속 계층 관계를 가로지르거나 다운캐스팅을 할 때 사용되는 캐스트 연산자이다.
  • dynamic_cast는 기본 클래스 객체에 대한 포인터나 참조자의 타입을 파생하는 클래스, 혹은 형제 클래스이 타입으로 변환해준다.
  • 다형성을 이용해 모호한 타입 캐스팅을 시도할 경우에 사용하게 된다.
  • dynamic_cast는 형변환을 동적으로 하기 때문에 그에 따른 타입을 질의해야할 때 사용된다.
  • 만약 캐스팅에 실패할 경우 NULL이 반환되어 형변환에대한 예외처리가 가능해진다.

다형성 / 단형성

클래스가 단순하게 상속 관계에 있다고 해서 이것이 다형성을 가진다고 말할 수 없다.

다형성을 가지려면 virtual 멤버 함수가 존재해야 하기 때문이다.

상속관계에 있지만 virtual 멤버 함수가 하나도 없다면 다형성을 가진게 아니라 단형성을 가졌다고 한다.

dynamic_cast에서는 다형성을 띄지 않은 객체간 변환은 불가능하며, 시도시 컴파일 에러가 발생한다.

C++ RTTI (Run Time Type Information)에 의존적이기 떄문에, 캐스팅 연산 비용도 꽤 비싼 편에 속하고, 연산 비용은 상속 체계의 복잡도와 깊이에 따라 증가한다.

Const Cast (const_cast)

  • 포인터나 참조의 상수성을 잠깐 제거하는데 사용한다.
  • volatile 키워드를 잠깐 제거하는데도 사용한다.
  • 형변환은 불가능하나 상수성을 제거하는 것에 사용가능하다.

포인터에서 상수성(const) 제거 / 추가 예제

#include <iostream>

int main()
{
	char msg[] = "Hello";
	const char* cp_msg = msg;
	std::cout << cp_msg << std::endl;
	//const char 이기때문에 변경 불가 (상수인 상태)
	cp_msg[0] = 'Y';

	char* copy = const_cast<char*>(cp_msg);
	//const_cast를 통해 상수성이 제거되어서 변경 가능
	copy[0] = 'Y';
	std::cout << copy << std::endl;
	return 0;
}

참조에서 상수성(const)제거 / 추가 예제

#include <iostream>

int main()
{
	int i = 100;
	const int& c_ref = i;
	std::cout << i << std::endl;

	//const int& 이기때문에 변경이 불가능하다.
	c_ref = 50;

	int& ref = const_cast<int&>(c_ref);
	ref = 50;
	std::cout << ref << std::endl;
	return 0;
}

const_cast로 상수성을 제거한 뒤 내용 변경이 가능하다. 또한 상수성을 추가할 수도 있다. 허나 대부분의 경우 상수성 제거의 목적을 위해 사용된다.

Reinterpret Cast (reinterpret_cast)

  • 캐스팅 대상을 캐스팅 타겟 타입으로 비트단위 재해석을 거치는 캐스트이다.
  • 연관성이 없는 포인터 타입을 변환하기 위해서 사용한다.
  • static_cast는 연관성이 어느정도 있거나 보장된 타입간의 캐스팅이 가능하지만, reinterpret_cast는 그런거 싹다 무시하고 비트단위로 캐스팅하며, C Style의 명시적 형변환과 비슷한 느낌이다.
  • 묵시적 캐스트가 불가능하다.
  • 캐스팅 결과가 컴파일러마다 다를 수 있다.
  • 정수 자료형의 경우 reinterpret_cast를 진행하면 메모리 주소값으로 변경할 수 있다. (*매우 위험)
  • 잘못 다룬다면 매우 위험한 캐스트 방식이다.
  • 다른 타입의 포인터로 변경할 경우 메모리 주소값이 유지되지 않는다.
  • 같은 타입일 경우엔 대부분 안전하나 안심할 수 없다.

reinterpret_cast의 예제

#include <iostream>

int main()
{
	int a = 123123;
	// 정수값이 메모리 주소로 변환되어 들어가 매우 위험
	int* pi = reinterpret_cast<int*>(a);

	int* b = &a;
	char* c;
	//int * -> char *로 타입캐스팅된다. 컴파일러에 따라 값이 조금씩 다르다.
	c = reinterpret_cast<char*>(b);

	return 0;
}

memcpy와 비슷한 방식이라 상당히 위험한 방식의 캐스트이다. 조심히 사용해야할 것 같다.

참고한 자료들

https://docs.microsoft.com/en-us/cpp/cpp/casting-operators?view=vs-2019

http://egloos.zum.com/sweeper/v/1907485

https://zbomoon.tistory.com/22

http://blog.naver.com/PostView.nhn?blogId=wkdghcjf1234&logNo=220210906503