추상클래스와 순수 가상 함수


구상 클래스

  • 객체를 생성 할 수 있는 클래스

  • 모든 멤버 함수가 구현되어 있어야 함

    • 지금까지 예시로든 모든 클래스는 구상 클래스

추상 클래스

  • 객체를 생성할 수 없는 클래스

  • 상속 계층구조에서 기본 클래스로 사용됨

  • 아주 일반적이어서 객체를 생성하기엔 맞지 않는 경우

    • 예를 들면 게임을 만들 때 모든 캐릭터는 움직일 수 있고 hp를 가지고 있다. 이를 공통 클래스로 만들어서 상속을 하는 것인데 이 공통 클래스의 객체는 만들 필요가 없다!
      • 만들어지는 객체는 Player, Enemy등의 객체
  • 추상 클래스는 하나 이상의 “순수 가상 함수”를 갖는다.

  • 즉, “순수 가상 함수”가 있는 클래스는 추상 클래스이다.

    • 멤버 함수의 선언 뒤에 “= 0”을 붙이면 순수 가상 함수가 된다.

    • 유도 클래스들은 반드시 기본 클래스의 순수 가상 함수를 오버라이드 해야 함
      - 오버라이드 하지 않는 경우 , 유도 클래스도 추상 클래스로 간주됨
      - 즉, 유도 클래스에서 특정 함수 구현을 “강제”하는 의미를 가짐

      virtual void function() = 0; // body가 없는 순수 가상 함수

추상 클래스를 사용하는 이유

  1. 기본 클래스에서의 구현이 적절하지 않을 때
    • 게임에서 모든 캐릭터는 움직임이 있지만 캐릭터 마다 다른 움직임을 갖고 있다. 이를 Base class에서 구현을 하는 것은 적절하지 않다, 즉 Base class를 상속받은 Derived class에서 구현하는 것이 더 합리적이다.

추상 클래스의 예시

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) = 0; // 순수 가상 함수, 즉 Entity는 추상 클래스가 된다.

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

};

이제 위의 Entity class는 추상 클래스가 되어 객체를 생성 할 수가 없어졌다. 이제 Derived class에서 추상 클래스의 내용을 오버라이드 해줘야 한다.

💡 왜 오버라이드를 해줘야 하는가?
  • 우리가 객체를 만드려면 해당 클래스의 모든 멤버들이 구현이 되어있어야 한다. 즉, 추상 클래스로 선언했기에 해당 구현을 Derived class에서 해줘야 객체를 생성 할 수 있다.

예시 코드

#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) = 0; // 순수 가상 함수, 즉 Entity는 추상 클래스가 된다.

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

};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player() = default;

	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) override //오버 라이드 완료
	{
		x += dx * 2;
		y += dy * 2;
	}

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

	
};

class temp : public Player {
private:
	int t;
public:
	temp(int x)
		:Player{0,0,0,0},t {x}{}
};

int main()
{
	Player p{ 1,1,1,1 }; // 생성 가능

}

주의

  • 추상 클래스는 객체를 생성할 수 없음
  • 하지만 여전히 추상 클래스의 포인터/참조자를 사용해 오버라이딩된 함수를 동적 바인딩 할 수 있음
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) = 0; // 순수 가상 함수, 즉 Entity는 추상 클래스가 된다.

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

};

class Player : public Entity
{
private:
	int hp;
	int xp;
public:
	Player() = default;

	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) override //오버 라이드 완료
	{
		x += dx * 2;
		y += dy * 2;
	}

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

	
}
int main()
{
	Player p{ 1,1,1,1 }; // 생성 가능

	Entity* eptr = new Player{ 1,1,10,10 }; //가능!
	eptr->Move(2,2);
	eptr->PrintPosition();
}

Interface


  • 순수 가상 함수”만”을 가진 추상 클래스인터페이스 클래스라 한다.

  • 클래스의 사용에 있어서 일반적인 기능을 묶어 놓은 클래스

  • 인터페이스 클래스를 기반으로 한 구상 클래스는 모든 기능( 함수/ 서비스)를 구현해야 함

  • 인터페이스는 껍데기다

  • 유도 클래스가 꼭 가져야 하는 기능들을 명시해 놓기 위해서 사용

  • 통상적으로 인터페이스 클래스의 이름을 지을 때는 대문자 I를 앞에 붙인다.

예시 코드

class IShape { //순수 가상 함수만을 가진 Interface Class
public:
	virtual void draw() = 0;
	virtual void rotate() = 0;
	virtual ~Shape();
};

class Circle : public IShape { // interface class를 상속받은 구상 클래스
public:
	virtual void draw() override {
		//Implemetation
	}

	virtual void rotate() override {
		//Implemetation
	}

	virtual ~Circle();
};
profile
개발로그

0개의 댓글