C++

순환참조,Inheritance(상속),업캐스팅

Nin 2020. 12. 18. 15:34
class Player
{
public:
   int Hp;
   int Att;

public:
   void Fight(Player& _Player)
   {
   
   }
   
   void Fight(Monster& _Monster)
   {
   
   }
};

class Monster
{
private:
   int Hp;
   int Att;
public:
   void Fight(Player& _Player)
   {
   
   }
   
   void Fight(Monster& _Monster)
   {
   
   }
};   

Player와 Monster를 만들고 서로 싸우는 코드를 작성하고 싶다고 했을때

int main()
{
   Player NewPlayer;
   Monster NewMonster;
   
   NewPlayer.Fight(NewMonster);
}

메인에서 이렇게 작성해주면 에러가 난다.

코드는 위에서 부터 아래로 읽히고 Player가 생성되고 

몬스터&를 인자로 받는 Fight함수가 실행될때는

몬스터에 대해서 알 방법이 없기 때문이다. 
그렇다고 Monster 클래스를 위로 올려도 똑같은 상황이다.

서로가 서로를 알어야 하기 때문에 이 역시 같은 에러가 난다.

여기서 extern 함수선언과 같은 문법을 사용할 수 있다.

class Monster;
class Player
{
public:
   void Fight(Monster& _Monster)
   {
   
   }
};

전방선언이라고 한다.Monster라는 클래스가 있을거야 라고 알려주는 문법이다.

void Fight(Monster& _Monster)
{
   _Monster.Hp -= Att;
}

그런 후 몬스터를 공격하는 함수를 이렇게 만들고 싶을것이다.

그치만 전방선언을 해준다고 해도 함수 정의에서 사용해버리면 

에러가난다.단순히 전방선언을 해주면 Monster라는 클래스가 있을거야를

알려주는 것이지 그 내부는 어떻게 되어있는지 알 수 없기때문이다.

Monster를 선언쪽에서 내부의 내용까지 다 사용하려고 하면

Monster 코드의 내용을 다 알아야 한다.

그렇기 때문에 선언과 정의를 나눠야 한다.

class Monster;
class Player
{
public:
   int Att;
   int Hp;
public:
   void Fight(Monster& _Monster);
};

class Monster
{
public:
   int Att;
   int Hp;
};

void Player::Fight(Monster& _Monster)
{
   _Monster.Hp -= Att
}

위와 같은 식으로 작성하면 사용이 가능해진다.

즉 선언만 존재하거나 참조를 복사할때 제외하고는

모두다 실제 내용을 알아야 한다.

위의 코드에서 볼때 Player 클래스에서 Monster라는 클래스를 사용하고 싶다면

2가지 단계를 거쳐야 한다.

1. Monster가 존재하는가를 알아야한다.

2. Monster의 내부 기능,변수 사용하려면 선언 전체를 알아야 한다.

1번만 해결하면 참조형(레퍼런스나 포인터형)은 사용할 수 있다.

void Fight(Monster _Monster)

1번을 만족했을때 위의 코드(값형으로)를 사용할 수 없는 이유는

Monster의 복사 생성자가 호출되고, 내부에 어떤 변수,함수가 있는지 알어야한다.

void Fight(Monster* _Monster)

이렇게 포인터(혹은 레퍼런스)를 써주면 포인터의 생성자가 호출되고

내부에 어떤 변수,함수가 있는지 알 필요가 없어진다. 

 

위의 코드에서 Player와 Monster는 기능이 누가 많고 누가 좀 더 적은지의

차이일뿐 개념적으로 같은 급수라고 볼 수 있다.(누가 먼저인지 정해져 있지 않다)

그렇기 때문에 서로가 서로를 알어야하고 서로를 전방선언해주고 사용한다.

서로가 서로를 알어야 하는 관계를 순환참조라고 하며

Player가 Monster를 알어야 하네?하고 Monster.h를 #iclude 해주면 에러가 난다.

Monster도 만들려면 Player를 알어야 하기 때문이다.

 

모든 C++의 문법은 프로그래머가 코드를 덜 작성하기 위해서 만들어졌다고 생각하자!

상속이 만들어진 이유는 같은 코드를 반복적으로 작성하는것을 피하기 위해 만들어 졌다.

클래스 간에 똑같은 변수,함수들을 하나의 클래스로 만들어서 이를
부모클래스로 만들고 부모클래스를 이용해서 부모클래스의
모든 기능을 가지고있는 클래스에 각 클래스마다 필요한 자신만의
기능을 추가해서 자식클래스로 만드는 것을 상속이라고 한다.

위의 Player 클래스와 Monster 클래스만 봐도 같은 멤버변수,함수가 보인다.

class FightObject
{
public:
   int Att;
   int Hp;
public:
   void Fight(FightObject* _OtherFightObject)
   {
      Hp -= _OtherFightObject->Att;
      _OtherFightObject->Hp -= Att;
   }
};

class Player : public FightObject
{

};

class Monster : public FightObject
{

};

이렇게 작성하는것이 상속이다.FightObject가 부모 클래스가 되고

Player와 Monster는 자식클래스가 되는것이고 자식클래스는

부모 클래스의 기능을 온전히 물려받는다.

(부모가 만약 한가지 방식으로 밖에 생성을 못한다면 내가(자식) 직접 호출해 줘야한다.

부모의 멤버이니셜라이즈 문법은 부모의 생성자를 호출할수 있는 문법을 지원한다.)

int main()
{
   Player NewePlayer;
   Monster NewMonster;
   
   NewPlayer.Att;
   NewMonster.Hp;
}

자식 클래스에는 아무런 내용이 없지만 위의 코드처럼 모든 기능을 사용할 수 있다.

class Player : public FightObject
{
public:
   int Level;
};

이때 자식클래스에만 필요한 기능을 추가 할 수있다.

위의 코드처럼 Player에만 Level을 추가할 수 있는 것이다.

그렇게 되면 Player의 멤버 변수는 Att,Hp,Level 이렇게 되는것이다.

메모리 적으로 보면

Att Hp Level

-------------FightObject의 8byte--------------Player의 4byte---

 

이렇게 Player의 Level은 부모의 변수 다음에 딱 붙어서 생성된다.

 

예를들어서 주소값을 출력해보면 Att는 1000 Hp는 1004 Level은 1008

이런식으로 나온다.

 

상속은 보통은 개념으로 이루어진 녀석을 표현하기 위한 문법이며,

위의 코드로 보면 싸우는 기능은 부모 클래스가 된것이고

플레이어는 싸우는 기능의 자식으로 들어가게 된것이다.

 

int main()
{
   Player NewePlayer;
   Monster NewMonster;
   
   NewPlayer.Fight(&NewMonster);
}

그럼 이제 싸우는 기능을 사용한다고 하면 부모클래스의

Fight 함수를 호출해서 위와같이 사용할 수 있다.

중요!!

void Fight(FightObject* _OtherFightObject)

Fight 함수를 보면 인자로 FightObject* 형인데 

함수를 사용할때 Monster의 참조형이 대입이 가능하다.

자료형이 다른데 대입이 가능하다는 것은

컴파일러가 형변환을 해주고 있다는것을 알 수 있다.

int* IntPtr = nullptr;
char* CharPtr = IntPtr;

위의 코드는 에러가 나는 코드이다.

포인터 변수에 다른 변수의 주소 값을 저장할 경우 

그 변수의 자료형과 포인터 변수가 가르켜야하는 자료형이

일치해야 하기 때문이다.

Player* PlayerPtr = nullptr;
FightObject* FightObjectPtr = PlayerPtr;

그런데 위와 같은 코드는 사용이 가능하다.

그냥 되는것이 아니라 특별하게 처리해 주고 있는 것이다.

부모 자식 클래스 구조에서 자식클래스의 참조형(레퍼런스나 포인터)이

부모클래스의 참조형이 되는것은 특별히 허용되고 구조적으로 권장된다.

이거를 업캐스팅이라고 한다.

 

                                                      -----Player만의 부분-----

------------FightObject--------------

Att Hp Level

-------------------------------Player----------------------------------

 

메모리 적으로 봐도 자식인 Player는 부모의 FightObject를 다 가지고 있다.

그렇기 때문에 형변환이 가능하고 허용해 주는 것이다.

'C++' 카테고리의 다른 글

상속문법(부모클래스 앞에 붙는 접근제한지정자)  (0) 2020.12.22
방어코드,Assert  (0) 2020.12.19
DefaultConstructer(기본 생성자)  (0) 2020.12.18
복습) 임시 변수,레퍼런스(reference)  (0) 2020.12.17
cout,GlobalClass  (0) 2020.12.16