본문 바로가기
CPP

CPP Casts

by yongckim 2021. 12. 4.
728x90
반응형
타입 캐스팅이란

현재 Type에서 다른 Type으로 변환하는 것을 의미합니다.

묵시적 형변환, 명시적 형변환

다음과 같이 서로 다른 자료형의 값을 대입하면 어떻게 될까요? 에러가 발생해서 프로그램이 종료될까요?

#include <iostream>

using namespace std;

int main(void) {
	double s = 333.3;
	int a = s;

	cout << s << endl;
	cout << a << endl;
}

위의 프로그램을 실행시켜보면 다음과 같이 잘 출력이 되는 것을 볼 수 있습니다.

어째서 서로다른 자료형에 바로 대입이 가능한걸까요?

 

그 이유는 대입하는 과정에서 묵시적 형변환(implicit type converison)이 일어났기 때문입니다.

 

묵시적 형변환이란 대입연산이나 산술연산에서 컴파일러가 자동으로 실행해주는 타입변환을 의미합니다.

 

대입연산을 할 대 연산자의 오른쪽에 존재하는 데이터의 타입이 왼쪽에 존재하는 타입으로 묵시적 형변환이 진행되며 산술연산에서는 데이터의 손실이 최소화되는 방향으로 묵시적 형변환이 이루어집니다.

 

만약, 제가 직접적으로 타입을 지정하고 싶을 경우는 어떻게 해야할까요?

 

이전에 C를 사용하셨다면 다음과 같이 자료형을 변환해봤을 수 있을 것입니다.

#include <iostream>

using namespace std;

int main(void) {
	double s = 333.3;

	cout << s << endl;
	cout << (int)s << endl;
}

(int)s 와 같은 형태로 앞에 변환할 타입을 명시해주는 타입 캐스팅 방법을 명시적 타입 변환(explicit type conversion)이라고 합니다.

 

명시적 타입 변환을 위해서는 다음과 같은 형태로 사용됩니다.

 

(바꾸려는 타입)대상

 

그런데 생각해보면 굳이 명시하지 않고 묵시적 형변환으로 처리해도 별차이 없는 것이라고 생각이 들 수 있습니다.

 

하지만 다음과 같이 int 타입의 변수 2개를 나누기 연산을 하는 상황 같은 경우 문제가 발생할 수 있습니다.

#include <iostream>

using namespace std;

int main(void) {
	int a = 3;
	int b = 2;
	double c = a / b;
	cout << c << endl;
}

해당 코드를 실행해볼 경우 double에는 a와 b를 나눈 몫만이 저장되는 것을 볼 수 있습니다.

해당 코드를 다음과 같이 변경하여 연산하면 원하는 값이 출력되는 것을 볼 수 있습니다.

#include <iostream>

using namespace std;

int main(void) {
	int a = 3;
	int b = 2;
	double c = (double)a / b;
	cout << c << endl;
}
CPP에서의 타입 캐스팅

명시적 형변환은 효과적으로 사용할 수 있지만 특정 상황에서 예측되지 못하는 상황을 만들 수 있습니다.

 

#include <iostream>

using namespace std;

int main(void) {
	int a = 33;
	std::cout << (char *)a << std::endl;
}

 

다음과 같은 상황에서 int 타입을 char *로 변환을 할 경우 정상적인 타입 캐스팅이 이루어지지 않기 때문에 하지 않아야 하지만 명시적 타입 변환을 사용해서 할 경우 컴파일이 되는 것을 볼 수 있습니다.

이를 실행시켜보면 프로그램이 비정상적인 종료를 하는 상황이 벌어지게 됩니다.

 

CPP에서는 이런상황에서 좀 더 안전한 타입 캐스팅 방식을 지원합니다.

 

static_cast

static_cast는 CPP에서 타입 캐스팅을 할 때 compile 타임에 형변환에 대한 오류를 잡아주는 연산자입니다.

 

명시적 타입 변환과 다른점은 잘 못된 변환을 시도시 컴파일 에러가 발생해서 못하도록 막아주는 역할을 할 수 있습니다.

 

static_cast는 다음과 같은 형식으로 사용합니다.

 

static_cast<바꾸려는 타입>(대상);

 

아까 int를 char *로 명시적 타입 변환을 시도했을 때는 컴파일 후 실행이 가능했지만 (정상적으로 실행되지는 않았지만) static_cast로 변경하고 실행하면 다음과 같이 에러가 발생하는 것을 볼 수 있습니다.

#include <iostream>

using namespace std;

int main(void) {
	int a = 33;
	std::cout << static_cast<char*>(a) << std::endl;
}

즉, 명시적 타입 변환에 비해 static_cast는 잘못된 타입 변환을 막아주기때문에 더 안전하게 사용할 수 있습니다.

 

const_cast

일반적으로 const로 선언한 타입은 한번 지정된 값을 다시 변경할 수 없습니다.

 

이때 타입 캐스팅을 이용해서 const로 선언한 값을 변경할 수 있습니다.

 

const_cast는 포인터 또는 참조자의 상수성을 잠깐 제거해주는데 사용됩니다. 주의할 점은 타입 변환은 불가능합니다.

 

다음은 const_cast를 이용한 const로 선언한 값을 변경하는 예제입니다.

#include <iostream>

using namespace std;

int main(void) {
	const string s = "hello";
	*(const_cast<string*>(&s)) = "bye";
	std::cout << s << std::endl;
}

reinterpret_cast

포인터가 다른 포인터 형식으로 변환할 수 있도록 하며, 정수 형식을 포인터로 변환하거나 그 반대로도 변환할수있는 연산자입니다.

 

자료형을 강제로 변환하기 때문에 위험하며, 변환된 값이 안전하다는 보장이 없습니다.

#include <iostream>

int main(void) {
	std::string s = "hello";
	uintptr_t ptr;
	std::string *ss;

	ptr = reinterpret_cast<uintptr_t>(&s);
	ss = reinterpret_cast<std::string*>(ptr);
	std::cout << &s << std::endl;
	std::cout << ptr << std::endl;
	std::cout << ss << std::endl;
}

dynamic_cast

dynamic_cast는 서로 상속 관계에 있는 클래스 간의 타입 변환시 사용됩니다. static_cast 같이 다른 캐스팅 연산자로도 가능하지만 dynamic_cast는 해당 타입 변환이 안전한지 검사를 해줍니다.

 

만약 안전하지않다면 NULL포인터를 반환하며 bad_cast라는 예외를 발생시킵니다. 해당 예외는 std::exception에 정의되어 있습니다.

 

dynamic_cast는 다운캐스팅(부모클래스 -> 자식클래스) 상황과 다중 상속 상황에서 클래스 간의 안전한 타입 캐스팅에 사용됩니다.

 

이 연산자를 사용하려면 하나 이상의 virtual 함수가 존재해야 합니다.

 

다음 예제는 다운 캐스팅시 dynamic_cast를 이용해서 다운 캐스팅을 하는 예제입니다.

#include <iostream>

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

class child : public parent {

};

int main(void) {
	child *c = dynamic_cast<child*>(new parent);
    // child *c = new parent; <- 다음 방식은 컴파일 에러가 발생함 (다운 캐스팅 불가)
}
반응형

'CPP' 카테고리의 다른 글

CPP Standard Template Library - iterator  (0) 2021.12.11
CPP Template  (0) 2021.12.09
CPP Exception - 3  (0) 2021.11.28
CPP Exception - 2  (0) 2021.11.28
CPP Exception - 1  (0) 2021.11.27