알 수 없는 사용자 2022. 1. 27. 21:30

다형성이란

다형성(Polymorphism = Poly + morph) = 겉은 똑같은데, 기능이 다르게 동작한다.

하나의 함수(겉이 똑같은)가 여러 클래스의 멤버함수(다른동작들)를 구동시킨다.

 

========================================================================

결론부터 말하자면 

//다형성(Polymorphism = Poly + morph) = 겉은 똑같은데, 기능이 다르게 동작한다.

다형성 방법
// 1. 오버로딩 (Overloading) = 함수 중복 정의 = 함수 이름의 재사용 // 인자로 구현
// 2. 오버라이딩 (Overrading)= 재정의 = "부모클래스의 함수"를 "자식클래스"에서 재정의 // 바인딩(virtual)기능 필요

// 바인딩 종류
// - 정적 바인딩(Static Binding)  : 컴파일 시점에 결정
// - 동적 바인딩(Dynamic Binding) : 실행 시점에 결정

// 일반함수는 정적 바인딩을 사용한다.
// 동적바인딩 하는방법 : virtual 로 가상함수로 만들어 사용
========================================================================

더보기
#include<iostream>
using namespace std;

//오늘의 주제 : 상속성

//객체지향 (OOP)
//상속성
//은닉성 - 캡슐화
//다형성

//다형성(Polymorphism = Poly + morph) = 겉은 똑같은데, 기능이 다르게 동작한다.
// - 오버로딩 (Overloading)	= 함수 ㅎ중복 정의 = 함수 이름의 재사용
// - 오버라이딩 (Overrading)= 재정의 = 부모 클래스의 함수를 자식클래스에서 재정의


class Player
{
public: //오버로딩
	void Move() { cout << "Move Player" << endl; }

	void Move(int a) { cout << "Move Player (int)" << endl; }

public:
	int _hp;
};

class Knight : public Player
{
public:
	void Move() { cout << "Move Knight" << endl; }
public:
	int _stamina;
};

class Mage : public Player 
{
public:
	int _mp;
};

void MovePlayer(Player* player)
{
	player->Move();
}

void MoveKnight(Knight* knight) //이렇게 사용 X
{
	knight->Move();
}

int main()
{
	//오버로딩
	Player p;
	int x = 1;
	p.Move();
	p.Move(x);

	Player p2;
	MovePlayer(&p2);
	Knight k;
	MoveKnight(&k);

	//
	Player p3;
	MovePlayer(&p3); // 플레이어는 플레이어다 ? YES
	//MoveKnight(&p3); // 플레이어는 기사다? NO 에러남
	
	MoveKnight(&k);  // 기사는 기사다? YES 
//!중 : 이렇게 하면 모든 하위객체들에 대한(MoveKnight() MoveMage()~~ )함수를 만들어 줘야 하기 떄문에 OOP에 맞지않다.
//따라서 MoveKnight()함수를 만들지않고도, 최상위객체(MovePlayer(&k))로 제어가 가능하다.
	MovePlayer(&k);  // 기사는 플레이어다 ? YES // Upcasting 임

	return 0;
}

이렇게 하위 객체에 대한 함수를 만들지 않고 

상위객체(Player클래스)만으로 하위객체(Knight,Mage클래스) 에 여러 함수를 제어 할 수 있다.

ex) 이런식으로 Monster 든 Player든 Creature라는 상위객채로 묶어서 전투시스템에서 인자로 Creature형 포인터를 받게 한다거나 해서 한방에 구현


// Knight를 Player로 변환해서 넘겨주는게 가능한 이유 : 
// 애당초 Knight를 만들때 Knight가 Player를 상속받아서 만들었고
// 따라서 Knight 의 생성자를 호출이 되면서 
// 간접적으로 Player의 생성자가 호출하니까
// 그림으로 따지자면 (메모리주소가 아래쪽으로 증가한다면 )
// [ [ Player ] ]
// [   Knight   ] 


여기서 문제는

Knight k;

MovePlayer(&k); 의 결과값이 우리가 원하는 결과가 안나온다는 것.

Player.move() 가 출력됨, 우리가 원하는 출력은 Knight.move()


해결 방법은 "바인딩"

// 바인딩(Binding) - 묶는다
// - 정적 바인딩(Static Binding)  : 컴파일 시점에 결정 // 일반함수는 정적 바인딩을 사용한다. 
// - 동적 바인딩(Dynamic Binding) : 실행 시점에 결정 // 동적바인딩하려면 virtual로 가상함수로 만들면 됨

바인딩이란 

접은글

더보기

변수에서 바인딩

    int num = 123;  이란 코드가 있을때

    num은 변수명 ,

    int는 자료형 ,

    123은 자료값  :  이라는 변수의 속성의 구체적인 값인데, 

    위와같이 이름, 자료형, 자료값에 각각 num,int,123이라는 구체적인 값을 할당(묶는(binding))하는 각각의 과정을 바인딩 이라고 한다.

 

함수에서 바인딩

    함수에서도 바인딩이 일어나는데,

    이때 바인딩은 어떤 코드에서 함수를 호출할 때 그 해당 함수가 위치한 메모리 주소로 연결(binding)해주는 것을 의미한다. 

    정적바인딩은 위의 함수binding을 compile시 확정시키고 프로그램 실행 시키는 것.

    동적바인딩은 위의 함수binding을 Runtime중에 정하며, 프로그램 실행 도중에 변경 가능( 인터프리터언어처럼, python)

 

 

동적바인딩 방법 코드 

Virutal로 가상함수를 만들어 동적바인딩 시킨다.

 

가상함수테이블 설명

결론 : 가상함수테이블( vftable )은 생성자와 함께 객체의 메모리공간에 할당 된다.

접은글

더보기

( 정확히는 메모리에 VMove()함수가 call하는 메모리주소 값(4byte)에 가상함수테이블시작주소(4byte)를 넣고,

  가상함수테이블[ { 4byte } X { virtual(가상)한 함수의 수 } ]에서 호출해야될 가상함수주소를 찾아 call해준다. )

 

VMove를 예로 들자면 ( Player의 Vmove) 와 ( Knight의 Vmove ) 의 가상함수 주소중,

실행중 불린 가상함수 주소가 가상함수 테이블에 할당 되는거임

 

가상함수테이블 만들어지는 과정

1. 생성자가 불리면, 해당 클래스에 대한 **가상함수 테이블 주소**를 **객체의 첫번째 메모리 공간에 넣는다.** 

2. 그 다음으로 멤버 변수의 값(_hp)이 메모리 공간에 쌓인다. 

3. 자식클래쓰(k)를 인자로 보낼 때 "vftable 주소 + 변수data" 같이 보내서 Knight k 인걸 알고 불러올 수 있다.

- 따라서 자식의 객체를 만들면, 부모 생성자가 불리면서 부모 클래스의 가상함수 테이블 주소가 자식 객체의 메모리 공간에 삽입되고, 

   - 자식 생성자가 불리면서 자식 클래스의 가상함수 테이블 주소가 메모리 공간에 덮어 써진다. 

- 참고로 가상함수 테이블에는 virtual 함수만 들어가 있고 아닌 멤버 함수는 가상 함수테이블에 없다. 

 

⇒ 이것들은 모두 디버깅으로 확인.

 

#include<iostream>
using namespace std;

//오늘의 주제 : 상속성

//객체지향 (OOP)
//상속성
//은닉성 - 캡슐화
//다형성

//다형성(Polymorphism = Poly + morph) = 겉은 똑같은데, 기능이 다르게 동작한다.
// - 오버로딩 (Overloading)	= 함수 ㅎ중복 정의 = 함수 이름의 재사용
// - 오버라이딩 (Overrading)= 재정의 = 부모 클래스의 함수를 자식클래스에서 재정의

// 바인딩(Binding) - 묶는다
// - 정적 바인딩(Static Binding)  : 컴파일 시점에 결정
// - 동적 바인딩(Dynamic Binding) : 실행 시점에 결정

// 일반함수는 정적 바인딩을 사용한다.
// 동적바인딩 하는방법 : virtual 로 가상함수로 만들어 사용

// 그런데 실제 객체가 어떤 타입인지 어떻게 알아서 가상함수를 호출해준걸까
// - 가상 함수 테이블 (vftable)

// .vftable [] 4byte(32) 8byte(64)

// [VMove] [ ]


class Player
{
public:
	Player()
	{
		//[ vftable ] 주소
		_hp = 100;
	}
	void Move() { cout << "Move Player" << endl; }

	virtual void VMove() { cout << "VMove Player" << endl; }

public:
	int _hp;
};

class Knight : public Player
{
public:
	Knight()
	{
		//[ vftable ] 주소
		_stamina = 100;
	}
	void Move() { cout << "Move Knight" << endl; }

	//가상 함수는 재정의를 하더라도 가상 함수다.
	virtual void VMove() { cout << "VMove Knight" << endl; }
public:
	int _stamina;
};

// Knight를 Player로 변환해서 넘겨주는게 가능한 이유 : 
// 애당초 Knight를 만들때 Knight가 Player를 상속받아서 만들었고
// 따라서 Knight 의 생성자를 호출이 되면서 
// 간접적으로 Player의 생성자가 호출하니까
// 그림으로 따지자면 (메모리주소가 아래쪽으로 증가한다면 )
// [ [ Player ] ]
// [   Knight   ]

void MovePlayer(Player* player)
{
	player->VMove();
}


int main()
{
	Player p3;

	printf("asd");
	Knight k;
	
	MovePlayer(&k); 

	return 0;
}

동적 바인딩 방법

virtual void VMove() { }  // 가상함수 사용

코드

더보기
#include<iostream>
using namespace std;

//오늘의 주제 : 상속성

//객체지향 (OOP)
//상속성
//은닉성 - 캡슐화
//다형성

//다형성(Polymorphism = Poly + morph) = 겉은 똑같은데, 기능이 다르게 동작한다.
// - 오버로딩 (Overloading)	= 함수 ㅎ중복 정의 = 함수 이름의 재사용
// - 오버라이딩 (Overrading)= 재정의 = 부모 클래스의 함수를 자식클래스에서 재정의

// 바인딩(Binding) - 묶는다
// - 정적 바인딩(Static Binding)  : 컴파일 시점에 결정
// - 동적 바인딩(Dynamic Binding) : 실행 시점에 결정

// 일반함수는 정적 바인딩을 사용한다.
// 동적바인딩 하는방법 : virtual 로 가상함수로 만들어 사용

class Player
{
public: //오버로딩
	void Move() { cout << "Move Player" << endl; }
	void Move(int a) { cout << "Move Player (int)" << endl; }

	virtual void VMove() { cout << "VMove Player" << endl; }

public:
	int _hp=0;
};

class Knight : public Player
{
public:
	void Move() { cout << "Move Knight" << endl; }

	//가상 함수는 재정의를 하더라도 가상 함수다.
	virtual void VMove() { cout << "VMove Knight" << endl; }

public:
	int _stamina=0;
};

class Mage : public Player
{
public:
	int _mp;
};

void MovePlayer(Player* player)
{
	player->VMove();
}

int main()
{
	Player p3;
	Knight k;
	MovePlayer(&p3); 
	
	MovePlayer(&k); 

	return 0;
}

Knight k;

MovePlayer( &k );

결과값이 우리가 원하는 대로 Knight의 VMove()함수가 동작했다..