Base Class Pointer
◆ 기본 클래스의 포인터
◆ C++에서 런타임 다형성의 구현을 위해 3가지 조건이 필요하다.
● 상속
● 기본 클래스 포인터 또는 참조자
● 가상 함수
동적 바인딩을 하기 위해서 3가지의 조건이 필요하며
저번 강의에서 가상함수를 배웠고, 저번 파트부터 강의 주제가 상속인 만큼 계속 상속에 대해 배우고 있다.
마지막으로 기본 클래스 포인터 또는 참조자 까지 사용하게 되면 동적 바인딩을 할 수 있다.
◆ 기본 클래스의 포인터 사용 예시
Entity* p1 = new Entity{ 0,0 };
Entity* p2 = new Player{ 0,0,2 };
Entity* p3 = new Enemy{ 0,0,2 };
Entity* p4 = new Boss{ 0,0,2 };
p1->Move(1, 1); // Entity:Move()
p2->Move(1, 1); // Player:Move()
p3->Move(1, 1); // Enemy:Move()
p4->Move(1, 1); // Boss:Move()
// delete pointers
Player 클래스의 기본 클래스는 Entity이다.
Entity의 포인터로 Player 객체를 가리킨다. (p2)
Entity* p2 = new Player {0, 0, 2};
이것이 기본 클래스의 포인터를 이야기한다.
Entity 의 포인터들로 Player , Enemy , Boss을 가리킬 수 있다.
이 상태에서 기본 클래스 (Entity) 의 Move() 함수가 가상 함수로 되어있을 때
Player, Enemy, Boss 유도 클래스들의 Move() 함수가
기본 클래스의 가상 함수를 오버라이딩을 하고 있으면
동적바인딩이 되어서
p2~p4 까지 Move() 함수는 각각의 Move() 함수가 호출된다.
#include <iostream>
using namespace std;
class Entity {
protected:
int x;
int y;
public:
Entity(int x, int y)
:x{ x }, y{ y } { }
~Entity()
{
cout << "Entity Destructor" << endl;
}
virtual void Move(int dx, int dy) {
x += dx;
y += dy;
}
void PrintPosition()
{
cout << x << ", " << y << endl;
}
};
class Player : public Entity {
private:
int hp;
public:
Player(int x, int y, int hp)
:Entity{x,y},hp{hp} { }
virtual void Move(int dx, int dy)
{
x += dx * 2;
y += dy * 2;
}
virtual ~Player()
{
cout << "Player Destructor" << endl;
}
};
int main()
{
Entity* ptr = new Player{ 1,1,10 };
ptr->Move(2, 2);
ptr->PrintPosition();
delete ptr;
/*
5, 5
Entity Destructor
*/
}
3,3 이 아닌 5,5가 되었음을 확인할 수 있다. Entity의 Move() 함수가 호출된 것이 아니라 Player의 Move() 함수가 호출된 것이다.
동적 바인딩으로 구현된 3가지 조건이 모두 만족되었다.
첫째, 상속되었다.
둘째, 기본 클래스에 있는 함수를 가상함수로 만들고 유도 클래스에 있는 함수 이름, 반환형, 인지가 모두 같은 함수를 오버라이딩하였다.
셋째, 기본 클래스의 포인터로 선언하였다. 조건 모두 다 만족하였다.
타입을 기준으로 호출할 함수를 판단하는 것이 아니라, 실제 메모리에 올라간 그 개체의 타입을 기준으로 함수가 호출이 되었다.
● 배열을 구성하여 활용 가능하다.
Entity* p1 = new Entity{ 0,0 };
Entity* p2 = new Player{ 0,0,2 };
Entity* p3 = new Enemy{ 0,0,2 };
Entity* p4 = new Boss{ 0,0,2 };
Entity* array[] = { p1,p2,p3,p4 };
for (int i = 0; i < 4; i++)
array[i]->Move(1, 1);
서로 다른 함수로 움직이는 유도 클래스 각각에 있는 Move() 함수를 각 개체에 Move() 함수 하나만 호출하는 배열을 돌려서 호출 할 수 있게 된다. 코드가 굉장히 간단해진다.
상속 초기에 Player 핸들러 클래스를 언급하면서 초기에는 Player 클래스만 있었으나, Enemy , Monster 클래스를 추가하려면 클래스마다 멤버 변수, for 문 등등 개별로 추가했어야 했으나, 이제는 하나의 for 문으로 호출할 수 있게 되었다.
서로 다른 로직을 가지고 있는 함수들을 만들어놔도, 알아서 개체 타입에 맞게끔 실행이된다. 이 것이 동적바인딩 사용 방법이다.
Using Base Class Reference
◆ 기본 클래스의 참조자를 활용한 다형성
Entity e{ 0,0 };
Entity& ref = e;
ref.Move(1, 1); // Entity::Move()
Player p{ 0,0,2 };
Entity& ref2 = p;
ref2.Move(1, 1); // Player::Move()
void MoveX(Entity& e, int dx)
{
e.Move(dx, 0);
}
* 기본 클래스의 참조자이기 때문에 동적 바인딩 된다.
MoveX(Entity& e, int dx) 함수를 만들고
인자로 2개를 받게 하였다.
하나는 Entity 의 참조자, 하나는 int 이다.
MoveX() 함수가 호출이 되면
e 의 Move() 함수가 호출이 되어, MoveX의 int 인자를 넘겨준다.
즉, e 라는 객체를 x 만큼 움직이라는 함수이다.
Player p{ 0,0,2 };
MoveX(p,1);
Player 를 만들고 나서, MoveX() 함수에 첫번째 인자로 p 를 넘겨줄 수 있었다.
왜? 기본 클래스가 사용된 곳에는 유도 클래스도 항상 사용할 수 있기 때문이다.
그 다음에 1 을 넘겨주면 x 방향으로 1 * 2 = 2 만큼 움직이게 된다.
이유는 e.Move(dx, 0); 함수가 동적 바인딩이 되기 때문에 가능하다.
Entity 의 Move() 함수가 아닌 Player 유도 클래스의 Move() 함수가 되고
예제에서
virtual void Move(int dx, int dy)
{
x += dx * 2;
y += dy * 2;
}
2배씩 움직이니깐 p 의 x 좌표가 2가 될 수 있다.
기본 클래스의 참조자이기 때문에 동적 바인딩이 되는 것이다.
참조자도 마찬가지이다.
int main()
{
Player player{ 3,3,20 };
Entity& ref = player;
ref.Move(2, 2);
ref.PrintPosition();
// 7,7
}
Entity 의 참조자가 Player 유도 클래스를 가리키도록 선언할 수 있다.
기본 클래스의 참조자이다.
ref 가 가지고 있는 Move() 함수를 호출하고 ref 의 PrintPosition() 을 호출해보면 7,7 가 나온다.
정적 바인딩이였다면 5,5 였지만 ref 가 실제로 가리키고 있는 타입 -> Player 클래스의 Move() 함수로 호출되기 때문에 7,7 이 나온다.
다형성을 활용하게 되면 굉장히 편리하게 코드를 사용할 수 있다.
Polymorphism
◆ 다형성과 동적 바인딩 : 기본 동작인 정적 바인딩은 컴파일 시 타입을 기준으로 호출 함수를 결정하지만, 동적 바인딩을 하게 되면 런타임 시 실제 메모리에 저장된 타입을 기준으로 호출 함수를 결정
◆ 가상 함수 : 컴파일러에게 동적 바인딩을 하려고 알려주기 위해서는 기본 클래스의 함수를 virtual 키워드를 통해 가상 함수로 만들어주어야 한다. 이런 경우, 소멸자도 항상 가상 함수로 선언 필요하다.
◆ 기본 클래스의 포인터 / 참조자 : 동적 바인딩을 위해서는 기본 클래스의 포인터 / 참조자로 유도 클래스를 가리키고 함수를 호출한다. (가상 함수가 아니면 기본 클래스 함수가 호출되니 주의)
◆ override / final 지정자
◆ 순수 가상 함수와 추상 클래스
◆ 추상 클래스와 인터페이스
https://youtu.be/KEu4o8xF7AU?si=7v0q5o8dcs2IVHZM
앞선 강의에 포인터와 참조자에대해 꾸준한 설명을 들어서 금방 이해가 갔다.
'C++ > C++ : Study' 카테고리의 다른 글
다형성(5) - 순수 가상 함수와 추상 클래스 (2) | 2025.05.29 |
---|---|
다형성 (4) - overrider & Final 지정자 (0) | 2025.05.18 |
다형성(2) - 동적 바인딩 & 가상 함수 (0) | 2025.05.01 |
다형성(1) - 소개 (0) | 2025.04.27 |
상속(8) - 멤버의 사용 (0) | 2025.04.17 |