[C++] 다형성, 오버로딩&오버라이딩

WestCoast·2022년 2월 4일
0

C, C++

목록 보기
5/12

다형성의 의미?


  • 프로그램 언어의 다형성(polymorphism)은 그 프로그래밍 언어의 자료형 체계의 성질을 나타내는 것으로, 프로그램 언어의 각 요소들(상수, 변수, 식, 오브젝트, 함수, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질을 가리킨다.
    반댓말은 단형성(monomorphism)으로, 프로그램 언어의 각 요소가 한가지 형태만 가지는 성질을 가리킨다.
  • 다형성( Polymorphism = Poly(여러가지) + morph(형체, 몸) ) = 겉은 똑같은데, 기능이 다르게 동작한다.

오버로딩(Overloading) & 오버라이딩(Overriding)


  • 메소드의 이름은 같은데 반환형, 매개변수, 구현부 등을 다르게 정의 해주는 경우 => 오버로딩
    주의할 점! : '매개변수'만을 다르게 오버로딩 하는 것은 불가능하다. <- 컴파일러가 인식하지 못함.
    void Move() { cout << "Move Player !" << endl; }
    // 오버로딩
    void Move(int a) { cout << "Move Player (int) !" << endl; }		
  • 메소드의 시그니쳐는 같은데 구현부를 재정의 해주는 경우 => 오버라이딩
    void Move() { cout << "Move Player !" << endl; }
    // 오버라이딩
    void Move() { cout << "Move Player Overriding !" << endl; }		

예제 코드


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

class Knight : public Player
{
public:
	// 오버라이딩
	void Move() { cout << "Move Knight !" << endl; }
};

void MovePlayer(Player* player)
{
	// 정적 바인딩 -> 컴파일타임에 결정
	// Player 객체가 매개변수로 들어올 것으로 예상하고 있음.
	// 매개변수에 Knight 객체가 들어와도 Player 클래스의 Move() 가 실행됨.
	player->Move();
}

void MoveKnight(Knight* knight)
{
	knight->Move();
}

int main()
{
	
	Player p;
	MovePlayer(&p);		// 플레이어는 플레이어다? YES
	// MoveKnight(&p);	// 플레이어는 기사다? NO <- 꼭 그렇진 않을 것(Error!)

	Knight k;
	MoveKnight(&k);		// 기사는 기사다? YES
	MovePlayer(&k);		// 기사는 플레이어다? YES <- 기사이기 이전에 플레이어임!

	return 0;
}

출력

Move Player !
Move Knight !
Move Player !

그런데 말입니다...


  • 이상한 점이 하나 있다. 위의 예제 코드에서
    Knight k;
    MovePlayer(&k);
  • 이 코드를 보면 Knight 객체를 넣어주었음에도 Player::Move() 메소드가 실행되었다는 것을 알 수 있음.
    Knight::Move() 메소드를 오버라이딩 해주었으니 상식적으로 Knight::Move()가 실행되어야 하는 것이 아닌가?
    왜 이런 상식과는 맞지 않는 결과가 출력된걸까?

정적 바인딩(Static Binding) & 동적 바인딩(Dynamic Binding)


  • 정적 바인딩은 일반적인 함수들은 거의 모두 해당된다고 생각하면 쉽다.
    우리가 위의 코드에서 사용하거나 오버로딩, 오버라이딩 했던 함수들은 모두 정적 바인딩 된 것이다.
    정적 바인딩은 '컴파일 시점'에서 결정된다.

  • 즉, 위의 MovePlayer(Player* player) 메소드는 컴파일 시점(프로그램 실행 전)에 자신의 성격을 결정하였고, 매개변수로는 Player 객체가 들어올 것이라고 이미 결정한 것이다.
    그러므로 매개변수로 Knight 객체가 들어왔다고 하더라도 Player::Move()를 호출한 것이다.

  • 하지만 이럴거면 우리는 굳이 오버라이딩을 할 이유가 없다.
    열심히 만들어 둔 Knight::Move()가 무용지물이 되지 않았는가?

  • 하지만 이를 위해 c++에서는 virtual 키워드를 제공하고 있다.
    이 키워드를 사용하면 '동적 바인딩'을 할 수 있게 되며, 해당 키워드를 사용한 함수는 '가상 함수'라고 칭한다.
    동적 바인딩을 하게 되면 함수의 성격은 런타임에 결정되며, 매개변수로 들어온 객체에 따라 Player::Move() 또는 Knight::Move() 를 호출할 수 있게 된다.


예제 코드


class Player
{
public:
	// 가상함수 <- 동적 바인딩을 써보자
	virtual void VMove() { cout << "Move Player !" << endl; }
};

class Knight : public Player
{
public:
	// 가상 함수는 재정의를 하더라도 가상 함수다!
	// virtual 키워드를 제거해도 여전히 가상 함수임.
	// virtual void VMove() == void VMove()
	virtual void VMove() { cout << "Move Knight !" << endl; }
};

void VMovePlayer(Player* player)
{
	// 동적 바인딩 -> 런타임에 결정 -> virtual 키워드
	// 매개변수에 들어오는 객체 종류에 따라 Player::VMove() 또는 Knight::VMove() 가 실행될 수도 있음.
	player->VMove();
}

int main()
{
	Knight k;
	VMovePlayer(&k);
    
    Player p;
    VMovePlayer(&p);

	return 0;
}

출력

Move Knight !
Move Player !

참고

위키백과 - 다형성 (컴퓨터 과학)

[자바][고급] 오버로딩, 오버라이딩의 런타임, 컴파일 시점 차이

profile
게임... 만들지 않겠는가..

0개의 댓글