TIL 2024/8/26

Sung Joo Lee·2024년 8월 26일

다형성과 동적 바인딩

  • 다형성
    • 정적 바인딩 (Compile-time)
      • 컴파일 타임에 어떤 함수를 호출 할지 선언 type에 따라 결정하는 것
    • 동적 바인딩 (Run - time)
      • 프로그램을 실행을 할 때 실제 메모리에 저장된 타입을 기준으로 어떤 함수를 호출할지 결정됨

        예시)

        Entity* eptr = new Boss{ 0,0,2};
        eptr -> Move(1,1); // Boss :: Move()
      • 실제 메모리에 Boss가 올라가 있고 eptr이 Boss를 가리키고 있기 때문에 Boss의 Move()가 실행된다.

  • 런타임 다형성
    • 런타임에서 같은 함수에 대해 다른 의미를 부여 ← 함수의 오버라이딩
  • 추상화된 프로그래밍을 가능하게 함
  • CPP에서 런타임 다형성(동적 바인딩)의 구현을 위해 아래와 같은 조건이 필요
    - 상속
    - 기본 클래스 포인터 또는 참조자
    - 가상 함수

가상 함수

  • Move()와 같이, 유도 클래스에서 기본 클래스의 함수를 재정의 또는 오버라이드해 사용할 수 있음

  • 오버라이드된 함수는 동적 바인딩을 통해 12,13 페이지의 예제와 같이 활용 가능

  • 오버라이드 될 수 있는 함수를 가상 함수라 함

    • 예제에서처럼, 기본 클래스의 함수 ( Entity :: Move )가 가상함수로 선언 후 , 유도 클래스에서 해당 함수를 오버라이드해서 구현하면 동적 바인딩 됨
  • CPP에서 런타임 다형성(동적 바인딩)의 구현을 위해 아래와 같은 조건이 필요( 중요함 )
    - 상속
    - 기본 클래스 포인터 또는 참조자
    - 가상 함수

  • 선언 방법

    • 오버라이드 할 함수를 기본 클래스에서 virtual로 선언 해주어야 함

    • 상속 계층구조에 있는 모든 해당 함수는 가상 함수가 됨

      class Entity{
      Public:
      			virtual void Move( int dx, int dy);
      			-----
      			
      };

      기본 클래스의 멤버 함수 앞에 virtual 키워드를 붙이면, 가상 함수가 됨

실제 예시

#include <iostream>

class Entity
{
protected:
	int x, y;
public:
	Entity(int x, int y)
		:x{ x }, y{ y } {}
	
void Move(int dx, int dy)
	{
		x += dx;
		y += dy;
	}

	void PrintPosition()
	{
		std::cout << x << "," << y << std::endl;
	}
};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player(int x, int y ,int hp , int xp)
		:Entity{x,y},hp{hp},xp{xp}{}

	void Move(int dx, int dy)
	{
		x += dx * 2;
		y += dy * 2;
	}
};

int main()
{

	Player e{ 1,1,10,10 };
	e.PrintPosition();

	e.Move(2,1);
	e.PrintPosition();

	return 0;
}
  • 위의 코드에서 Player e는 정적 바인딩으로 인해 class안에 정의되어 있는 Move를 실행한다. 하지만 우리는 오버라이딩을 사용하여 동적 바인딩을 통해 Entity의 Move를 사용 해보도록 하자.

오버라이딩 ( 가상 함수)

#include <iostream>

class Entity
{
protected:
	int x, y;
public:
	Entity(int x, int y)
		:x{ x }, y{ y } {}
	
	virtual void Move(int dx, int dy)//가상 함수 ,유도 클래스에서 오버라이딩 가능
	{
		x += dx;
		y += dy;
	}

	void PrintPosition()
	{
		std::cout << x << "," << y << std::endl;
	}
};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player(int x, int y ,int hp , int xp)
		:Entity{x,y},hp{hp},xp{xp}{}

	virtual void Move(int dx, int dy)
	//여기서는 virtual 사용 할 필요없지만 협업을 위한 명시적 표현이다.
	{
		x += dx * 2;
		y += dy * 2;
	}
};

int main()
{

	Player e{ 1,1,10,10 };
	e.PrintPosition();

	e.Move(2,1);
	e.PrintPosition();

	return 0;
}
  • 위의 코드와 같이 virtual 선언을 해주어 유도 클래스에서 오버라이딩이 가능하게 만들어 준다.

  • overriding을 하기 위해서는 함수명, 함수 반환형, 매개변수가 같아야 오버라디잉이 가능하다.

    • 오버로딩과 다르다..
  • 유도 클래스의 오버라이딩한 함수도 virtual을 사용하여 명시적으로 표현을 해주어 협업자가 굳이 다른 코드를 찾아보지 않아도 파악이 가능하게 해준다.

가상 함수의 선언 ,유도 클래스에서 할일

  • 오버라이드 할 함수를 유도 클래스에서 구현

  • 함수 원형과 반환형이 기본 클래스의 가상 함수와 일치해야 함!

  • 유도 클래스의 함수에서는 virtual 키워드를 넣지 않아도 되지만, 혼동을 피하기 위해 명시해 주는 것을 권고

  • 유도 클래스에서 함수를 오버라이드 하지 않으면, 기존과 같이 기본 클래스의 함수가 상속됨

	class Player : public Entity
	{
		public:
					virtual void Move(int dx, int dy);
					-----
			
			
	}

가상 소멸자

  • 유도 객체를 올바른 순서로, 올바른 소멸자를 사용해 소멸시키는 방법이 필요

  • 해결 방법 ? → 가상 소멸자

    • 클래스가 가상 함수를 가지면, 항상 가상 소멸자를 함께 정의해야 함
    • 마찬가지로, 기본 클래스의 소멸자가 가상 소멸자면, 유도 클래스의 소멸자도 가상 소멸자
#include <iostream>

class Entity
{
protected:
	int x, y;
public:
	Entity(int x, int y)
		:x{ x }, y{ y } {}

	~Entity()
	{
		std::cout << "Entity 소멸자 호출됨" << std::endl;
	}
	
	virtual void Move(int dx, int dy)//가상 함수 ,유도 클래스에서 오버라이딩 가능
	{
		x += dx;
		y += dy;
	}

	void PrintPosition()
	{
		std::cout << x << "," << y << std::endl;
	}
};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player(int x, int y ,int hp , int xp)
		:Entity{x,y},hp{hp},xp{xp}{}

	~Player()
	{
		std::cout << "Player 소멸자 호출됨" << std::endl;
	}

	virtual void Move(int dx, int dy)
	{
		x += dx * 2;
		y += dy * 2;
	}
};

int main()
{
	Entity* e = new Player{ 1,1,10,10 };

	delete e;

	return 0;
}

결과


'

코드를 실행 시키면 우리는 Player의 소멸자는 호출되지 않고 Entity의 소멸자만 호출이 되는 것을 볼 수 있다. 우리는 Player 객체를 생성을 했기 때문에 Player 소멸자가 호출이 되어야만 한다.

해결

#include <iostream>

class Entity
{
protected:
	int x, y;
public:
	Entity(int x, int y)
		:x{ x }, y{ y } {}

	virtual ~Entity()
	{
		std::cout << "Entity 소멸자 호출됨" << std::endl;
	}
	
	virtual void Move(int dx, int dy)//가상 함수 ,유도 클래스에서 오버라이딩 가능
	{
		x += dx;
		y += dy;
	}

	void PrintPosition()
	{
		std::cout << x << "," << y << std::endl;
	}
};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player(int x, int y ,int hp , int xp)
		:Entity{x,y},hp{hp},xp{xp}{}

	virtual ~Player()//명시적으로 보여줌
	{
		std::cout << "Player 소멸자 호출됨" << std::endl;
	}

	virtual void Move(int dx, int dy)//명시적으로 보여줌
	{
		x += dx * 2;
		y += dy * 2;
	}
};

int main()
{
	Entity* e = new Player{ 1,1,10,10 };

	delete e;

	return 0;
}

간단하다. virtual을 ~Entity()에 붙여서 가상 소멸자로 만들어 주면된다.

결과


-보면 헷갈릴수도 있는데 간단하게 생각해보면 내가 유도 클래스에서 정의한 동일한 이름의 함수는 ( 생성자, 소멸자 포함) 가상 함수로 선언을 하지 않으면 호출이 되지 않는다!

profile
개발로그

0개의 댓글