[c++] Type Casting

Kim Dongil·2023년 9월 8일
0

C++

목록 보기
18/23

static_cast

C 스타일에 암시적 명시정 형변환은 컴파일시 타입에 대한 오류를 잡아내지 못한다. (추후 런타임에 에러가 남)

int main()
{
	char a = 10;
	int *p = (int*)&a;
    *p = 5; //런타임 에러
   	return 0;
}

static_cast<new_type>(expression)

컴파일 타임에 형변환에 대한 타입을 변환
논리적으로 변환 가능한 타입을 변환한다.

그렇기 때문에 컴파일시 타입에 대한 오류를 잡아 준다는 장점이 있다.

단 포인터 타입을 다른 것으로 변환 하는 것을 허용하지 않는다. 그러나 상속 관계에 있는 포인터 끼리 변환이 가능하다.

포인터 형변환 예시 (오류)

#include <iostream>

using namespace std;

int main()
{
	// char* -> int*
	char str[] = "Hello World";
	int* ptr2;
	ptr2 = static_cast<int*>(str);

	// int* -> char*
	int tmp = 10;
	int* ptr3 = &tmp;
	char* c = static_cast<char*>(ptr3);
}

주의 해야하는 상황 1
const 임에도 불구하고 의도하지 않게 정상작동한다.

#include <iostream>

using namespace std;

int main()
{
	const int num = 10;
	int* ptr = (int*)&num;
	*ptr = 30; // const int 가 변해버림
	cout << *ptr << endl;
}

주의 해야하는 상황 2

#include <iostream>

using namespace std;

class Test
{
public:
	int m_test = 10;
};

int main()
{
	int a = 5;
	long long b = a; // 암시적 형변환. 컴파일러가 형을 변환해준다.

	Test* t = (Test*)&a; // 명시적 형변환. 프로그래머가 형변환을 위한 코드를 직접 작성한다.
	cout << t->m_test << endl;
}

올바른 예시

#include <iostream>

using namespace std;

int main()
{
	double d = 1.0;
	float f = 1.f;

	int tmp_int = static_cast<int>(d);	// double -> int 로 형변환
	cout << tmp_int << endl;

	float tmp_float = static_cast<float>(d);	// double -> float 로 형변환
	cout << tmp_int << endl;

	double tmp_double = static_cast<double>(f);	// float -> double 로 형변환
	cout << tmp_int << endl;
}

상속관계에서의 static_cast는 upcast는 문제가 없지만
downcast(static_cast<자식클래스>(부모클래스))시에는 unsafe 하게 동작할 수 있다.
downcast를 안전하게 사용하려면 dynamic_cast 사용하자

upcast downcast 예시
upcast는 올바르게 동작하는 방면 downcast 는 의도치 않은 결과가 나온다.

아래 코드를 보면 downcast상황에서 triangle타입인 pt 가 OnlyTriangle()를 불렀더니 이상한 값이 나왔다
shape s는 triangle을 생성하지 않았는데 접근했기 때문이다.

#include <iostream>

using namespace std;

class shape
{
private:
	int a;

public:
	virtual void Draw() { cout << "shape : called Draw()" << endl; }
};

class triangle : public shape {
private:
	int b;

public:
	triangle() { b = 30; }

	void Draw() { cout << "triangle : called Draw()" << endl; }
	void OnlyTriangle() { cout << "OnlyTriangle()" << endl; cout << b << endl; }
};

int main()
{
	// 1. upcast
	shape* ps;
	triangle t;
	ps = static_cast<shape*>(&t); // 자식 -> 부모
	cout << "1. upcast" << endl;
	ps->Draw();

	// 2.downcast
	shape s;
	triangle* pt;
	cout << "2. downcast" << endl;
	pt = static_cast<triangle*>(&s); // 부모 -> 자식
	pt->Draw();
	pt->OnlyTriangle();
}

C 스타일 형변환은

  • 어떤 형변환인지 명확하지 않다.
  • 한 실수를 컴파일러가 캐치하지 못할 수도 있다

그렇기에 좀 더 안전하고, 사용자의 의도를 며확히 하기 위해, c++ 스타일의 형변환을 사용하자.

const_cast

const<new_type>(expression)

포인터 또는 참조형의 상수형을 잠깐 제거해주는데 사용된다.
volatile키워드를 잠깐 제거해 주는 데에도 사용이 가능

#include <iostream>

using namespace std;


int main()
{
	// 포인터 상수형 제거
	char str[] = "apple";
	const char* ptr = str;
	cout << "before : " << str << endl;

	//ptr[0] = 'Q'; //error

	char* c = const_cast<char*>(ptr); // const char* -> char*
	c[0] = 'Q';
	cout << "after : " << str << endl;

	// 참조형 상수성 제거
	int temp = 15;
	const int& ref = temp;
	cout << "before : " << temp << endl;

	// ref = 20; // error
	
	int& r = const_cast<int&>(ref);
	r = 30;
	cout << "after : " << temp << endl;
}
  • const로 선언된 변수에 대해서는 const_cast를 사용하지 않는다.
  • const_cast를 사용할 때에는 변수에 대해 포인터를 새로 만들고 사용해야 한다.

reinterpret_cast

reinterpret_cast<new_type>(expression)

임의의 포인터 타입기리 변환을 허용하는 캐스트 연산자
저수형을 포인터로 바꿀 수도 있다.
expression에 해당하는 것을 new_type으로 비트단위로 바꾸는 것

#include <iostream>

using namespace std;


int main()
{
	int a = 0x0102;

	// 1. int -> int* 로 타입캐스팅
	// 변수 a의 값을 절대주소로 받는 포인터 ptr1
	// 이 경우에는 주소 000번지를 가리키고 있는 pointer 가 된다.
	// 어느 곳을 가리킬지 모르기 때문에 위험하다.
	int* ptr1 = reinterpret_cast<int*>(a);

	// 2. int* -> char* 로 타입캐스팅
	// 컴파일에 따라 다르게 나온다.
	int* ptr2 = &a;
	char* c = reinterpret_cast<char*>(ptr2);

	// 0201 0000 순서대로 들어가는데 char 타입이므로 0이 들어가서 0이 출력된다.(컴파일 따라 다름)
	cout << "2. int* -> char* (cout) : " << *c << endl;

	// 3. struct 내의 첫번째 int -> int*
	// struct cube에는 int 형 변수 하나만 존재하므로,
	// ptr3는 int a변수의 시작점을 잘 가리키고 있다.
	struct Cube { int a; };
	Cube cb;
	cb.a = 20;

	int* ptr3;
	ptr3 = reinterpret_cast<int*>(&cb);
	cout << "3. struct -> int* : " << *ptr3 << endl;
}

dynamic_cast

dynamic_cast<new_type>(expression)

  • 안전한 다운캐스팅에 사용된다.

  • 부모 클래스의 포인터에서 자식 클래스의 포인터로 다운 캐스팅 해주는 연산자.

  • 런타임 시간에 실제로 해당 타입이 다운 캐스팅이 가능하지 검사

  • Class의 포인터 간 형 변호나 시, 안전하게 down casting을 위해 사용한다.

  • Class의 참조변수 간 형 변환 시, 안전하게 down castring을 위해 사용한다.
    단, parent에 virtual 함수가 존재해야 정상 동작한다.

성공할 경우 : new_type의 value를 return 한다.
실패할 경우(new_type = point) : null pointer
실패할 경우(new_type = reference) : bad_cast(exception 처리된다.)

부모클래스의 생성자로 생성되었고 부모 포인터가 가리키고 있는 클래스를 자식클래스 포인터로 형변환 할 때 runtime error (자식클래스는 생성되지도 않았으므로.)

자식클래스의 생성자로 생성되었고 부모클래스 포인터가 가리키고 있는 클래스를 자식클래스로 형변환 할 때 OK

자식클래스의 생성자로 생성되었고 자식클래스를 포인터가 가리키고 있는 클래스를 부모클래스로 형변환 할 때는 dynamic_cast를 사용하지 않고 static_cast를 사용한다. (upcast)

#include <iostream>

using namespace std;

class Base
{
public:
	Base() { cout << "Base()\n"; };
	virtual ~Base() { cout << "~Base()\n"; };

	void Show() {
		cout << "This is Base Class\n";
	}
};

class Derived : public Base
{
public:
	Derived() { cout << "Derived()\n"; };
	virtual ~Derived() { cout << "~Derived()\n"; };

	void Show() {
		cout << "This is Derived Class\n";
	}
};

int main(void)
{
	Base* pBase = new Derived();
	pBase->Show();

	Derived* pDerived = dynamic_cast<Derived*>(pBase);
	if (pDerived == nullptr)
	{
		cout << "Runtime Error\n";
	}
	else
	{
		pDerived->Show();       //Derived 로 생성되었으므로 문제없음
	}

	delete pBase;
	system("pause");
	return 0;
}

static_cast 와 c 스타일 캐스트의 차이

0개의 댓글

관련 채용 정보