상세 컨텐츠

본문 제목

C++ Cast 연산자

C++/C++98

by deulee 2023. 8. 9. 12:47

본문

C++에는 총 네 가지의 Cast 연산자가 있는데 정리된 것 부터 알아보도록 하겠다.

캐스트 연산자 변환 형태
static_cast 상속 관계의 클래스 포인터 및 레퍼런스. 기본 타입. 타입 체크 안함
dynamic_cast 상속 관계의 클래스 포인터 및 레퍼런스. 타입 체크. RTTI 기능 필요
const_cast const, volatile 등의 속성 변경
reinterpret_cast 포인터끼리, 포인터와 수치형간의 변환

그럼 하나씩 차례대로 알아보도록 하자.


1. static_cast

"static_cast" 연산자는 지정한 타입으로 변경하는데 무조건 변경하는 것이 아니라 "논리적으로 변환 가능한 타입"만 변환한다.

 

기본적인 문법은 다음과 같다.

static_cast<타입>(대상)

예시

#include <iostream>

class Parent {};

class Child : public Parent {};

void main(void)
{
	char* str = "string";
	int* pi;
	double = d = 123.456;
	int i;
	
	i = static_cast<int>(d); // 가능;
	pi = static_cast<int *>(str) // 불가능
	pi = (int *)str; // 가능
    
	Parent P, *pP;
	Child C, *cC;

	pP = static_cast<Parent *>(&C); // 가능
	pC = static_cast<Child *>(&P); // 가능하지만 위험
	pP = static_cast<Paernt *>(&i); // 에러
	return 0;
}

정수형과 실수형, 상호 호환되는 열거형과 정수형과의 변환, double과 float의 변환 등을 허용한다.

 

하지만 포인터의 타입을 다른 것으로 변환하는 것은 허용하지 않는다.

 

포인터끼리 변환할 때는 상속 관계에 있는 포인터끼리만 변환이 가능하며 상속 관계가 아니면 변환을 거부한다.

 

자식 -> 부모 = upcasting = "pP = static_cast<Parent *>(&C);" // 안전

부모 -> 자식 = downcasting = "pC = static_cast<Child *>(&P);" // 위험

 

이 변환이 위험한 이유는 "pC"로 부모에게 없는 멤버 함수를 호출하게 될 경우 어떤 일이 일어날지 예측할 수 없기 때문이다. 물론 PC로 상속받은 멤버만 참조한다면 안전할 것이다.


2. dynamic_cast

"dynamic_cast"는 포인터끼리 또는 레퍼런스끼리 변환하는데 반드시 포인터는 포인터로 변환하고 레퍼런스는 레퍼런스로 변환해야 한다.

 

그리고 포인터끼리 변환할 때는 "static_cast"와 마찬가지로 상속 계층에 속한 클래스끼리만 변환할 수 있다.

 

그럼 뭐가 다를까?

 

바로 위험한 다운 캐스팅을 상황에 따라서 허용하지 않는다는 것이다.

 

다음 예시를 보자.

#include <iostream>

class Parent
{
public:
	virtual void printMe()
	{
		std::cout << "I am Parent" << std::endl;
	}
};

class Child : public Parent
{
private:
	int num;
public:
	Child(int an) : num(an)
	{}
	virtual void printMe()
	{
		std::cout << "I am Child" << std::endl;
	}
	void printNum()
	{
		std::cout << "Hello Child = " << num << std::endl;
	}
};

int main(void)
{
	Parent P, *pP, *pP2;
	Child C(1), *pC, *pC2;

	pP = &P;
	pC = &C;

	pP2 = dynamic_cast<Parent *>(pC); // upcasting 항상 안전
	pC2 = dynamic_cast<Child *>(pP2); // pP2가 가리개는 객체의 타입이 정적 타입과 같으므로 가능
	printf("pC2 = %p\n", pC2);
	pC2 = dynamic_cast<Child *>(pP); // 불가능
	printf("pC2 = %p\n", pC2);
	return 0;
}

출력 결과

우선 위의 예제를 보면 upcasting은 잘 되는 것을 볼 수 있다.

 

하지만 downcasting의 경우는 될 때도 있고 안될 때도 있는데 어떤 차이가 있을까?

 

  1. 첫 번째 downcasting
    • 첫 번째의 경우 타입이 "Parent *"인 "pP2"를 "Child *"로 downcasting하고 있는 상황이다. 하지만 "pP2"가 가리키고 있는 객체는 "C"이고 캐스팅하고자 하는 타입과 일치하기 때문에 캐스팅은 성공하는 것이다.
    • 즉, 가리키는 객체의 실제 타입 == 캐스팅하고자 하는 타입 -> 캐스팅 성공
  2. 두 번째 downcasting
    • 지금의 경우는 타입이 "Parent *"이고 실제로 가리키고 있는 객체 또한 "P"인 "pP"를 "Child *"로 downcasting 하고 있는 상황이다. 이때 가리키는 객체의 실제 타입하고 캐스팅하고자 하는 타입이 다르기 때문에 캐스팅에 실패하게 된다.
    • 가리키는 객체의 실제 타입 != 캐스팅하고자 하는 타입 -> 캐스팅 실패
    • NULL 반환

이 연산자가 이런 형식이 가능한 이유는 RTTI 옵션이 켜져 있어야 하며 변환 대상 타입들끼리는 상속 관계에 있어야 하고 최소한 하나 이상의 가상 함수를 가져야 한다. 그게 아니고서야 굳이 대입할 필요도 없고 캐스팅이 불필요하다.

 

결론적으로 "dynamic_cast 연산자는 포인터가 가리키는 대상이 캐스팅하고자 하는 타입을 가리키고 있을 때만 변환을 허용함."

 

이를 이용하여 예전에 배웠던 typeid를 대신해서 dynamic_cast를 이용하여 사용하는 예제를 보여주겠다.

void func(Parent* p)
{
	p->printMe();
	Child* c = dynamic_cast<Child *>(p);
	if (c) {
		c->printNum();
	}
	else {
		std::cout << "이 객체는 num을 가지고 있지 않다." << std::endl;
	}
}

이렇게 이 연산자는 실행중에 타입 점검을 할 수 있을 뿐만 아니라 캐스팅까지 할 수 있기 때문에 typeid 연산자보다 편리하다.

 

그리고 레퍼런스끼리도 변환이 가능한데 대신, 레퍼런스는 NULL을 할당할 수 없으므로 에러가 발생할 때 bad_cast 예외를 발생한다.


3. const_cast

"const_cast" 연산자는 포인터의 상수성만 변경하고 싶을 때 사용한다.

#include <iostream>

int main(void)
{
	char str[] = "string";
	const char *c1 = str;
	char *c2;

	c2 = const_cast<char *>(c1);
	c2[0]='a';
	std::cout << c2;
	return 0;
}

위의 예시는 "c1"의 상수성을 제거함으로써 "c2"를 통해 값을 수정할 수 있는 것을 볼 수 있다.

 

하지만 그렇다고 해서 상수성만 제거할 수 있는 것이지 다른 타입으로의 변환이 가능하다는 것은 아니다.


4. reinterpret_cast

"reinterpret_cast" 연산자는 임의의 포인터 타입끼리 변환을 허용하는 상당히 위험한 캐스트 연산자다.

 

심지어 정수형과 포인터간의 변환도 허용한다. 즉, 정수형값을 포인터 타입으로 바꾸어 절대 번지를 가리키도록 한다거나 할 때 이 연산자를 사용한다.

int* pi;
char* pc;
pi = reinterpret_cast<int *>(12345);
pc = reinterpret_cast<char *>(pi);

일종의 강제 변환이라서 안전하지도 않고 이식성도 없다.

 

이 연산자는 포인터 타입간의 변환이나 포인터와 수치형 데이터의 변환에만 사용하며 기본 타입들끼리의 변환에느 사용할 수 없다.


출처

http://www.soen.kr/lecture/ccpp/cpplec.htm

 

C/C++ 강좌

 

www.soen.kr

 

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

C++ 멤버 포인터 연산자  (0) 2023.08.09
C++ RTTI  (0) 2023.08.09
C++ 예외의 비용  (0) 2023.08.09
C++ 예외 지정  (0) 2023.08.09
C++ 표준 예외  (0) 2023.08.08

관련글 더보기