상속에 대한 얘기

2021. 11. 24. 22:48c,c++ 기본/next step

320x100

안녕하세요 대학생 개발자입니다.

 

이번글에서는 상속에 관한 얘기를 좀 해보고 싶습니다.

사실 상속을 얘기하려면 생성자부터 모조리 다시 얘기를 해야됩니다.ㅠㅠ(물론 예전처럼 하지는 않을겁니다)

 

상속이란건 말그대로 그 객체의 모든 정보를 내가 이어 받는거죠. 그정보를 이어받아서 내가 추가를 하든 아니면 필요없는건 제외를 하든 그렇게 사용하라는겁니다.

뭐... 상속에서 public protected private상속 일때 접근을 어떻게하는가 이런건... 뭐 건너뛰겠습니다 그런걸 논할 글은 아니라고 생각합니다.

 

우선 상속을 할때 부모의 생성자가 존재한다면 그 밑의 클래스 즉 하위클래스들은 전부다 생성자가 존재해야됩니다. 이유는 당연하죠 하위 클래스의 생성자에서 상위클래스의 생성자를 호출해야되기 때문이죠.

 

그리고 상속하면 절대 빼먹을 수 없는 부분이 가상함수인데요 우선 가상함수라는게 뭔지부터 알아봐야 될 것 같습니다.

가상함수라고하면 부모의 클래스에서 가지고 있는 가상함수를 오버라이딩 했을때 부모의 클래스의 포인터로 그 함수를 호출한다면 vftbl을 통해서 실제 그 함수포인터의 테이블에 존재하는 함수를 콜해주는 것을 가상함수 동적바인딩이라고하죠

 

그럼 우리는 vftbl의 존재에 대해서 알아봐야할것 같아요

가상함수를 호출하면 내부에서 동적바인딩이라는 것을 한다는걸 모두 알고있을겁니다.

그럼 가상함수를 동적 바인딩 한다는게 무슨 의미인가..? 가상함수는 어떤원리로 호출이 되는가를 좀 짚고 넘어가보겠습니다.

우선 가상함수 테이블이라는 것부터 알아봐야겠는데요.

클래스에 가상함수가 하나이상 존재한다면(상속받은것 포함)

자신이상의 상위클래스가 가진 가상함수 갯수가 되는 것입니다. (자신포함이죠)

class Test1
{
public:
	virtual void Func();
};

class Test2:public Test1
{
public:
	virtual void Func();
    virtual void Func2();
};

class Test3:public Test2
{
public:
    virtual void Func2();
    virtual void Func3();
};

class Test4:public Test3
{
public:
	virtual void Func();
    virtual void Func3();
};

이런 클래스들이 존재한다면..

Test1의 vftbl의 사이즈는 1이될겁니다 왜냐면 자신 포함 자신상위의 클래스에 가상함수를 가진 클래스중 가장 많은 가상함수를 가진 클래스가 자기 자신이기 때문입니다.

그럼 밑의 경우도 동일하겠죠.

Test2의 경우에는 2개있을것이고 Test3의 경우에는 3개 Test4의 경우에도 3개가 존재할겁니다 Test4의 경우에도 3개겠죠.... Test2 이후로는 자신 이상의 클래스에서 받아온 가상함수 + 자신의 가상함수 갯수가 3개이기 때문입니다. 

자 그럼 각 테이블에 어떤게 있는지 알아보겠습니다.

 

Test1의 가상함수테이블에는

당연히 Test::Func하나만 있을겁니다.

 

Test2의 가상함수 테이블에는

Test2::Func와

Test2::Func2가 있을겁니다.

 

Test3의 가상함수 테이블은

Test2::Func

Test3::Func2

Test3::Func3 이렇게 존재할거구요... 그쵸?

상위 클래스에 Func라는 함수도 가상함수니까 아래에 영향을 주게 될겁니다.

 

그리고 마지막 Test4의 가상함수테이블은

Test4::Func

Test3::Func2

Test4::Func3 이 존재하는겁니다.

물론 이에대한 세팅은 생성자에서 해주는거구요.

 

여기서 이제 가상함수를 호출한다고하면

1. 가상함수인지 판단 -> 그함수가 가상함수라면 -> vftbl에서 그걸 call한다. 이렇게 되는겁니다. 동적바인딩의 과정 생각보다 그렇게 어렵지 않고 쉽게 받아들여 질겁니다.

 

음... 그리고 순수가상함수인데요...

순수가상함수를 만들게되면 순수가상함수는 무조건 자식에서 무조건 받아야 되는 함수가 됩니다.

즉 내가 안쓰고 싶다고해서 오버라이딩을 안하면 에러가 나는거죠 그때부터는 실제 call할 대상이 없으니까 말이에요.

말그대로 함수포인터에 nullptr을 넣는겁니다 실제 선언도 그렇잖아요?

virtual void Func() = 0;

이런식으로 하는거니까요 ㅋㅋㅋ...

 

사실 저는 게임을 개발하는 입장이다 보니까 이를 게임에서 어떻게 쓰는게 가장 좋을까에 대한 생각을 해보지 않을수가없었어요. 물론 정답은 이미 어느정도 추론하고있었습니다만...

아마 언리얼이나 유니티만 다뤄 보셨다 하더라도 이정도에 대해서는 알고있을겁니다.

언리얼에서는 UObject, Unity에서는 MonoBehaviour라는 최상위 클래스를 둡니다.

이건 전부 상속으로 이루어져 있는거죠

UObject를 상속받은 객체를 상속받고... 이런게 이어져서 Actor가 되는것이고

MonoBehaviour을 상속받은 객체를 상속받고 이런게 이어져서 GameObject가 되는것이니까요.

 

우리의 게임도 이런식으로 구현이 될 수있습니다.

void Update()
{
	for(0~objectList.size())
    	objectList[i]->Update();
};

C언어로 만든 절차지향적인것보다 훨씬더 코드는 깔끔히 나왔습니다. 대신 단점도있습니다.

당연히 가상함수 콜은 느립니다. 아까 위의 로직을 거쳐야되기 때문이죠...

당연한거겠지만 편리성과 성능은 거의 반비례구조이기 때문이죵... 

하지만 이렇게 해놓으면 코드 수정도 편하고 좋잖아요... 향후 보완성도 좋아지고...

사실 게임에서 가장 c++스러운 코딩은 이게 아닐까싶어요... 물론 네트워크가 붙은 경우라면 다르겠지만.

당장 콘솔로 게임을 만들라고하면 저형태가 가장 자연스러운 게임의 형태가 될 것 같아요.

 

음... 그리고 만약 부모의 기능을 확장하는 상속이 아니라 다형성을 위한 상속이라면... abstract class 로 만들어도 좋을 것 같습니다. 위의 BaseObject와 같은 느낌으로말이죠...

 

아.. 맞다 이얘기를 깜빡할뻔 했군요 가장중요한게 있습니다.

상속관계에서 소멸자는 반드시 가상소멸자여야 한다는겁니다.

뭐... 왜그런지는 다들 알고계시겠지만 혹시 까먹으시는 분들 계실까봐 알려드립니다. 사실 저위에 저도 했었어야됬는데 깜빡하고 안적었으니까요 ㅋㅋㅋㅋㅋ...

 

그럼... 오늘은 여기서 글을 마무리하겠습니다.

이번글은 다행히 길지는 않군요... 째뜬 읽어주신 여러분들 감사합니다.

다음엔 더 좋은 정보를 가지고 오도록 하겠습니다.

그럼 안녕히계세요

320x100

'c,c++ 기본 > next step' 카테고리의 다른 글

class에 대한 얘기(2)  (0) 2021.11.24
template와 class에 대한 얘기(1)  (3) 2021.11.23
new와 delete에 대한 얘기  (0) 2021.11.20
메모리와 c++에 관한 얘기  (0) 2021.11.14
DLL,LIB, FILEIO에 대한얘기  (0) 2021.11.13