요약

업캐스팅: 파생 클래스의 객체를 기본 클래스의 포인터로 가리키는 것

다운캐스팅: 기본 클래스 포인터가 가리키는 객체를 파생 클래스의 포인터로 가리키는 것

정적 타입 (Static Type) - 포인터가 선언될 때의 타입

동적 타입 (Dynamic Type) - 포인터가 실행 중에 가리키고 있는 대상체의 타입

“부모는 자식을 가리킬 수 있다.”

주제

저번 포스트에 추상화, 상속성, 다형성 개념을 정리하고 게임을 만들며 실습해봤다.

이번엔 작동 원리를 공부해보자.

부모와 자식 클래스 객체 간 타입 캐스팅은 어떻게 이루어지는지 알아보자.

추상화, 상속

먼저 기본 클래스 Unit과 파생 클래스 Monster 를 다음과 같이 만들고자 한다.

image

#include <iostream>
#include <vector>
using namespace std;

class Unit
{
protected:
	string name;
	int hp;
	int atk;
public:
	Unit() = default;
	Unit(string _name, int _hp, int _atk) :
		name(_name), hp(_hp), atk(_atk) {}
	~Unit() {}
public:
	void PrintInfo()
	{
		cout << "name: " << name << '\n';
		cout << "hp: " << hp << '\n';
		cout << "atk: " << atk << '\n';
	}
};

class Monster : public Unit	// 상속
{
private:
	int grade;
public:
	Monster() = default;
	Monster(string _name, int _hp, int _atk, int _monsterGrade) :
		Unit(_name, _hp, _atk), grade(_monsterGrade) {}
	~Monster() {}
public:
	void PrintInfo() 
	{
		Unit::PrintInfo();
		cout << "grade: " << grade << '\n';
	}
};

enum eMonsterGrade { normal, chief, boss };

int main()
{
	Unit unit("유닛", 10, 1);
	Monster monster("몬스터", 50, 5, normal);

	unit.PrintInfo();
	cout << '\n';
	monster.PrintInfo();
}

name: 유닛
hp: 10
atk: 1

name: 몬스터
hp: 50
atk: 5
grade: 0

그 결과 다음과 같이 생성될 것이다.

image

객체와 포인터

갑자기 변덕이 든다.

Unit 타입이었던 애를 몬스터처럼 바꾸고 싶다.

Monster 타입이었던 애를 기본 유닛처럼 바꾸고 싶다.

상속 관계의 클래스 간에는 포인터로 서로 가리킬 수 있지 않을까?

객체를 가리키는 포인터를 만들어

부모는 자식을, 자식은 부모를 가리키도록 만들어 보자.

#include <iostream>
#include <vector>
using namespace std;

class Unit
{
protected:
	string name;
	int hp;
	int atk;
public:
	Unit() = default;
	Unit(string _name, int _hp, int _atk) :
		name(_name), hp(_hp), atk(_atk) {}
	virtual ~Unit() {}
public:
	virtual void PrintInfo()
	{
		cout << "name: " << name << '\n';
		cout << "hp: " << hp << '\n';
		cout << "atk: " << atk << '\n';
	}
};

class Monster : public Unit	// 상속
{
private:
	int grade;
public:
	Monster() = default;
	Monster(string _name, int _hp, int _atk, int _monsterGrade) :
		Unit(_name, _hp, _atk), grade(_monsterGrade) {}
	~Monster() override {}
public:
	void PrintInfo() override
	{
		Unit::PrintInfo();
		cout << "grade: " << grade << '\n';
	}
};

enum eMonsterGrade { normal, chief, boss };

int main()
{
	Unit unit("유닛", 10, 1);
	Monster monster("몬스터", 50, 5, normal);

	Unit* pUnit;
	Monster* pMonster;

	pUnit = &unit;			// 당연히 가능
	pMonster = &monster;		// 당연히 가능
	
	pUnit = &monster;		// 가능. 업캐스팅
	pUnit->PrintInfo();		// 호출

	cout << '\n';

	// pMonster = &unit;		// 에러
	pMonster = (Monster*)&unit;	// 가능. 다운캐스팅
	pMonster->PrintInfo();		// 호출
}

name: 몬스터
hp: 50
atk: 5	// grade가 나오지 않았다.

name: 유닛
hp: 10
atk: 1
grade: -858993460	// grade가 쓰레기값으로 초기화 되었다.

어라.. 의문점이 2가지나 생겼다.

  1. 자식 객체가 부모 객체를 가리키지 못한다.
  2. 포인터로 가리킨 타입이 아닌 처음 선언된 타입의 함수를 호출해버린다.

업캐스팅, 다운캐스팅

첫 번째 의문점은

pUnit = &monster;	// 가능

는 잘 되는데

// pMonster = &unit;	// 에러

는 별도로 타입 캐스팅을 해주어야 한다는 점이다.


업캐스팅: 파생 클래스의 객체를 기본 클래스의 포인터로 가리키는 것

pUnit = &monster;	// 업캐스팅

다운캐스팅: 기본 클래스 포인터가 가리키는 객체를 파생 클래스의 포인터로 가리키는 것

pMonster = &pUnit;	// 에러
pMonster = (Monster*)&unit;	// 다운캐스팅. 명시적 타입 변환 필요


C++에서는 상속 관계의 클래스 간 대입 시 좌변이 더 상위의 클래스 타입이면 캐스팅을 하지 않고 직접 대입이 허용된다.

즉, 부모는 자식을 가리킬 수 있다.

반면 좌변이 하위의 클래스일 경우 명시적 타입 캐스팅을 통해 가리켜야 한다.

정적 타입 vs 동적 타입

두 번째 의문점은 다음과 같다.

  1. pUnit은 monster를 가리켰지만, unit의 PrintInfo 함수를 호출했다.
  2. pMonster는 unit을 가리켰지만, monster의 PrintInfo 함수를 호출했다.


정적 타입 (Static Type) - 포인터가 선언될 때의 타입

동적 타입 (Dynamic Type) - 포인터가 실행 중에 가리키고 있는 대상체의 타입

pUnit = &unit;		// 정적 타입
pUnit = &monster;	// 동적 타입

C++에선 포인터가 선언될 때의 정적 타입을 보고 이 타입에 맞는 함수를 호출한다.

그렇다면 어떻게 가리키고 있는 타입(동적 타입)에 따라 멤버 함수가 선택되도록 할 수 있을까?

이제서야 가상 함수가 등장한다.

다음 포스트에서 가상 함수의 원리를 공부하며 어떻게 동적 타입에 따라 멤버 함수가 선택될 수 있는지 알아보자.


참고

http://www.soen.kr/


※ 틀리거나 개선할 부분이 있다면 알려주세요.

태그:

카테고리:

업데이트:

댓글남기기