본문 바로가기
개발/C, C++

C++ - 형변환 연산자 (casting operators)

by 피로물든딸기 2021. 8. 26.
반응형

C, C++ 전체 링크

 

static_cast<> 컴파일 시 형변환
dynamic_cast<> 런타임 시 형변환
const_cast<> 상수형 포인터에서 const 제한을 제거
reinterpret_cast<> C의 형변환과 유사

 

static_cast

 

static_cast는 형변환이 적절한지 컴파일 시 체크한다.

즉, 적절한 형변환이 아니면 강제로 형식을 변환하지 않는다.

링크에서는 TEST class에서 (int) 변환에 대한 type casting을 정의해줬으므로 적절환 형변환이 가능했고,

static_cast로 형변환이 적절하다고 판단할 수 있다.

 

static_cast는 파생 클래스의 형식을 기본 형식(부모 클래스)으로 캐스팅하는 것을 허용한다.

 

이전 예제에서 아래의 코드를 추가해서 실행해보자.

#include <stdio.h>
#include <iostream>

using namespace std;

class FOOD
{
private:
	int price = 0;
public:
	FOOD() { printf("FOOD created\n"); }
	virtual ~FOOD() { printf("FOOD deleted\n"); }

	virtual void printName() { cout << "FOOD Class" << endl; }
	virtual void printPrice() { cout << this->price << endl; }
	void printLine() { cout << "=================" << endl; }
};

class SNACK : public FOOD
{
private:
	int price = 1000;
public:
	SNACK() { printf("SNACK created\n"); }
	~SNACK() { printf("SNACK deleted\n"); }

	/* override 시 compile error */
	void printName(int value) { cout << "SNACK Class, value : " << value << endl; }

	void printPrice() { cout << this->price << endl; }

	/* override 시 compile error */
	void printLine() { cout << "------------------------" << endl; }
};

class MILK : public FOOD
{
private:
	int price = 500;
public:
	MILK() { printf("MILK created\n"); }
	~MILK() { printf("MILK deleted\n"); }

	void printName() { cout << "MILK Class" << endl; }
	void printPrice() { cout << this->price << endl; }
};

int main(void)
{
	FOOD* foodList[3];

	foodList[0] = new FOOD();
	foodList[1] = new MILK();
	foodList[2] = new SNACK();

	printf("\n---- TEST START ----\n\n");

	for (int i = 0; i < 3; i++)
	{
		foodList[i]->printName();
		foodList[i]->printPrice();
		foodList[i]->printLine();
		putchar('\n');
	}

	printf("\n\n---- static_cast TEST ----\n\n");

	// 적절한 형변환
	SNACK* snack1 = static_cast<SNACK*>(foodList[2]);
	//snack1->printName(); // SNACK에는 printName(void)가 없다.
	snack1->printName(5);
	snack1->printPrice(); 
	snack1->printLine();

	// downcasting 자식 클래스 타입 - 부모 클래스 인스턴스
	SNACK* snack2 = static_cast<SNACK*>(foodList[0]);

	// snack->printName(); //foodList -> SANCK으로 변환, 이제 FOOD가 아니므로 printName(void)는 불가
	snack2->printName(5);
	snack2->printPrice(); // 비정상적인 값 출력
	snack2->printLine();

	// MILK* milk = static_cast<MILK*>(snack); // MILK는 SNACK이 아니므로 compile error

	// upcasting 부모 클래스 타입 - 자식 클래스 인스턴스
	FOOD* food = static_cast<FOOD*>(snack2); // SNACK을 다시 FOOD 로 변환

	//food->printName(5); // FOOD이므로 printName(int)는 불가
	food->printName();
	food->printPrice();
	food->printLine();

	return 0;
}

 

foodList[2]는 FOOD 타입이지만 SNACK 객체이다.

즉, foodList[2]가 가르키는 것이 SNACK인 것이 명확하므로, 파생 형식에 대한 포인터로 형변환이 가능하다.

→ static_cast가 이것을 컴파일 타임에 허용한다.

FOOD* foodList[3];
foodList[2] = new SNACK();

// 적절한 형변환
SNACK* snack1 = static_cast<SNACK*>(foodList[2]);
//snack1->printName(); // SNACK에는 printName(void)가 없다.
snack1->printName(5);
snack1->printPrice(); 
snack1->printLine();

 

FOOD (부모)를 SNACK (자식)으로 변형 하였다. - downcasting

가능은 하지만 자식 클래스의 멤버 함수(printPrice)를 부르면 엉뚱한 값이 출력된다.

따라서 사용을 자제하는 것이 좋다.

FOOD* foodList[3];
foodList[0] = new FOOD();

// downcasting 자식 클래스 타입 - 부모 클래스 인스턴스
SNACK* snack = static_cast<SNACK*>(foodList[0]);

// snack->printName(); //foodList -> SANCK으로 변환, 이제 FOOD가 아니므로 printName(void)는 불가
snack->printName(5);
snack->printPrice(); // 비정상적인 값 출력
snack->printLine();

 

MILK와 SNACK은 부모/자식 관계가 아니다. 따라서 static_cast가 컴파일에 에러를 일으킨다.

MILK* milk = static_cast<MILK*>(snack); // MILK는 SNACK이 아니므로 compile error

 

SNACK (자식)을 FOOD (부모)로 변형하였다. - upcasting

// upcasting 부모 클래스 타입 - 자식 클래스 인스턴스
FOOD* food = static_cast<FOOD*>(snack); // SNACK을 다시 FOOD 로 변환

//food->printName(5); // FOOD이므로 printName(int)는 불가
food->printName();
food->printPrice();
food->printLine();

dynamic_cast

 

동적으로 생성된 객체가 기본 형식인 경우, 구체적으로 어떤 객체에 대한 인스턴스인지 확인할 때 사용한다.

(RTTI, Run-Type Type Information, Identification)

 

위의 예제에서 main문을 아래와 같이 수정하자.

int main(void)
{
	bool test = false;
	
	FOOD* food = nullptr;
	
	if (test) food = new MILK();
	else food = new SNACK();

	MILK* milk = dynamic_cast<MILK*>(food);
	
	if (milk != NULL) printf("test = true, I'm milk!\n");
	else
	{
		SNACK* snack = dynamic_cast<SNACK*>(food);
		if (snack != NULL) printf("test = false, I'm snack\n");
	}
	
	return 0;
}

 

test가 true인 경우 / false인 경우 출력 결과는 아래와 같다.

 

부모 타입의 FOOD에 대해 객체는 test = true / false에 따라 MILK / SNACK이 된다.

dynamic_cast가 실패하는 경우 NULL을 출력하기 때문에, 현재 객체의 인스턴스가 무엇인지 확인할 수 있다.

 

좀 더 자세히 확인하기 위해 다음의 결과를 보자.

int main(void)
{
	bool test = false;
	
	FOOD* fd = new FOOD();
	SNACK* sn = new SNACK();
	MILK* mk = new MILK();

	printf("\n\n --------- dynamic test --------- \n\n");

	FOOD* dc1 = dynamic_cast<FOOD*>(fd);
	FOOD* dc2 = dynamic_cast<SNACK*>(fd);
	FOOD* dc3 = dynamic_cast<MILK*>(fd);

	printf("dc1 fd - fd : %x\n", dc1);
	printf("dc2 sn - fd : %x\n", dc2);
	printf("dc3 mk - fd : %x\n", dc3);

	putchar('\n');

	FOOD* dc4 = dynamic_cast<FOOD*>(sn);
	FOOD* dc5 = dynamic_cast<SNACK*>(sn);
	FOOD* dc6 = dynamic_cast<MILK*>(sn);

	printf("dc4 fd - sn : %x\n", dc4);
	printf("dc5 sn - sn : %x\n", dc5);
	printf("dc6 mk - sn : %x\n", dc6);

	putchar('\n');

	FOOD* dc7 = dynamic_cast<FOOD*>(mk);
	FOOD* dc8 = dynamic_cast<SNACK*>(mk);
	FOOD* dc9 = dynamic_cast<MILK*>(mk);

	printf("dc7 fd - mk : %x\n", dc7);
	printf("dc8 sn - mk : %x\n", dc8);
	printf("dc9 mk - mk : %x\n", dc9);

	putchar('\n');

	return 0;
}

 

SNACK, MILK의 인스턴스는 FOOD가 아니므로 NULL을 반환하였다. (dc2, dc3)

MILK / SNACK도 서로가 서로의 인스턴스가 아니므로 NULL을 반환하였다. (dc6, dc8)

하지만 MILK / SNACK은 FOOD이므로 dynamic_cast이 가능하다.

 

이제 FOOD* fd = new FOOD()를 new SNACK()으로 변경해보자.

FOOD* fd = new SNACK();

 

fd는 SNACK 인스턴스지만 FOOD*로 dynamic casting 하는데 문제가 없다. (dc1)

그리고 fd는 SNACK 인스턴스이므로 SNACK*으로 dynamic casting이 가능해졌다. (dc2)

 

최종 코드는 아래와 같다.

#include <stdio.h>
#include <iostream>

using namespace std;

class FOOD
{
private:
	int price = 0;
public:
	FOOD() { printf("FOOD created\n"); }
	virtual ~FOOD() { printf("FOOD deleted\n"); }

	virtual void printName() { cout << "FOOD Class" << endl; }
	virtual void printPrice() { cout << this->price << endl; }
	void printLine() { cout << "=================" << endl; }
};

class SNACK : public FOOD
{
private:
	int price = 1000;
public:
	SNACK() { printf("SNACK created\n"); }
	~SNACK() { printf("SNACK deleted\n"); }

	/* override 시 compile error */
	void printName(int value) { cout << "SNACK Class, value : " << value << endl; }

	void printPrice() { cout << this->price << endl; }

	/* override 시 compile error */
	void printLine() { cout << "------------------------" << endl; }
};

class MILK : public FOOD
{
private:
	int price = 500;
public:
	MILK() { printf("MILK created\n"); }
	~MILK() { printf("MILK deleted\n"); }

	void printName() { cout << "MILK Class" << endl; }
	void printPrice() { cout << this->price << endl; }
};

int main(void)
{
	bool test = false;
	
	FOOD* fd = new SNACK();
	SNACK* sn = new SNACK();
	MILK* mk = new MILK();

	printf("\n\n --------- dynamic test --------- \n\n");

	printf("\n --------- FOOD type fd = instance SNACK --------- \n\n");

	FOOD* dc1 = dynamic_cast<FOOD*>(fd);
	FOOD* dc2 = dynamic_cast<SNACK*>(fd);
	FOOD* dc3 = dynamic_cast<MILK*>(fd);

	printf("dc1 fd - fd : %x\n", dc1);
	printf("dc2 sn - fd : %x\n", dc2);
	printf("dc3 mk - fd : %x\n", dc3);

	putchar('\n');

	FOOD* dc4 = dynamic_cast<FOOD*>(sn);
	FOOD* dc5 = dynamic_cast<SNACK*>(sn);
	FOOD* dc6 = dynamic_cast<MILK*>(sn);

	printf("dc4 fd - sn : %x\n", dc4);
	printf("dc5 sn - sn : %x\n", dc5);
	printf("dc6 mk - sn : %x\n", dc6);

	putchar('\n');

	FOOD* dc7 = dynamic_cast<FOOD*>(mk);
	FOOD* dc8 = dynamic_cast<SNACK*>(mk);
	FOOD* dc9 = dynamic_cast<MILK*>(mk);

	printf("dc7 fd - mk : %x\n", dc7);
	printf("dc8 sn - mk : %x\n", dc8);
	printf("dc9 mk - mk : %x\n", dc9);

	putchar('\n');

	return 0;
}
반응형

'개발 > C, C++' 카테고리의 다른 글

C++ 스마트 포인터 : auto_ptr  (0) 2021.10.10
C++ 스마트 포인터 (Smart Pointer)  (0) 2021.10.03
C++ - virtual / override 키워드  (0) 2021.08.25
C++ - 가상 소멸자 (Virtual Destructor)  (0) 2021.08.23
C++ - explicit 키워드  (0) 2021.08.22

댓글