[C++] 상속, 가상함수 Lu's…〃 Programing。

 C++, 가상 함수에 대해. C++ 수업을 배웠을 때에만 해도, 정확히는 실제로 사용하는 코드를 보기 전까지만 해도 이게 뭐 하는 놈인가 싶어 쓰기는 커녕 거들떠도 안 봤다.

 가상함수란 것은 상속에서 의미를 가지는 함수인데, 일단 상속이란 것부터 알아보자. 상속이란 무엇인가? 그것은 바로 부모 클래스의 private을 제외한 모든 멤버변수와 멤버함수를 쓸 수 있는 자식클래스를 만드는 것이다. 물론 상속받은 자식클래스에서 부모클래스형 변수의 protected에 있는 변수를 바꿀 수는 없다. 예를 들어, class parent가 있을 경우, class child : public parent라는 자식클래스를 만들어 parent를 상속했다. 여기서

class parent{
protected:
int i;
public:
parent(){i = 5;}

};

class child : public parent{
private:
parent *papa;
public:
void get_papa(parent *p){papa = p;}
void makeFamily{papa->i = 10;}
};

 이런 식으로 makeFamily 함수에서 parent 클래스 papa의 변수 i의 값을 변경하려고 할 수는 없다. 참조 역시 불가능하다. 상속을 표현할 때 "부모의 물건은 내 것, 내 것도 내 것"이라는 표현을 쓰는데 틀렸다. 정확히 말하자면 "부모의 유전자는 내 것, 내 유전자도 새로 만들 수 있어!"라고 표현하는 게 맞다. 부모의 물건은 자식클래스도 못 건들거든. 

 그럼 상속이란 걸 왜 하는가? 상속을 하는 이유는 하나, 코드를 줄이기 위해서이다. 동일한 코드를 쓰는 두 개 이상의 클래스가 있다고 가정하자. 이 클래스에 토씨 하나 안 틀리고 같은 코드가 3000줄 정도 된다고 했을 때, 그 프로젝트는 완전히 같은 코드가 3000줄이나 존재한다.

 이게 뭔가 좀 더 있어보이겠다는 착각을 불러일으킬지언정 문제가 나오면 각 클래스마다 수정을 해야하는 노가다를 해야한다. 하지만 부모 클래스에서 상속을 받을 경우, 그럴 필요 없이 부모 클래스만 수정을 해주면 간단히 문제가 해결된다. 물론 두 개정도밖에 안되는 작은 클래스면 문제 없지만, 예를 들어보자.

 몬스터라는 클래스가 있다. 이 밑으로 오크, 고블린, 임프 등의 몬스터가 수십 종이 있다. 이 몬스터들은 등장할 때 자신의 이름을 외친다. 오크는 "오크!", 고블린은 "고블린!"같이. 이 경우, 각 몬스터마다 shout(){cout<<"오크!"<<endl;} 같이 하드코딩을 해도 되지만 이러기엔 너무 힘들다. 이럴 때 상속을 이용한다. 부모 클래스에서 

class monster{
private:
char name[25];
public:
monster(){ lstrcpy(name,"몬스터");}
void shout(){cout<<name<<"!"<<endl;}
};

같은 식으로 코딩을 해놓고 개별 몬스터 종류마다

class ork : public monster{
public:
ork(){ lstrcpy(name,"오크");}

};
같이 이름만 지정해놓으면 각각 자기 이름을 호명하며 등장하게 된다.


 여기까지는 아직 상속을 왜 써야할지 의문일 것이다. 하지만 상속의 진가는 여기서 드러난다.

C++11의 비장의 무기, STL의 list를 통해 모든 몬스터 종류의 리스트를 코드 몇 줄로 다 호출할 수 있다!

ex ) 개별몬스터마다 초기화가 돼 있다고 가정

list<monster*> list_monster;
ork n_okr;
goblin n_goblin;
imp n_imp;
.
.
.
list_monster.push_back(&n_ork);
list_monster.push_back(&n_goblin);
list_monster.push_back(&n_imp);


for (auto p : list_monster)
{
p->shout();
cout << endl;
}

>오크!
>고블린!
>임프!

이런 식으로, 만약 수십 종의 몬스터를 개별적으로 관리하기보다는 list_monster라는 리스트 하나로 관리하는 것이 편하고 관리가 쉽다. (STL의 list는 별도의 자료를 찾아보고 써먹어보자. 위 코드는 클래스만 만들면 실제 돌아가는 코드이다.)

 이 때, 특별한 몬스터 뱀파이어가 있다고 가정해보자. 뱀파이어는 지능이 높아 등장할 때 '피의 혈족이여...'라며 등장한다. 근데 이건 shout 함수와 다르다. 이런 '특수한 상황'을 위해 가상함수라는 것이 쓰인다.

 먼저, 부모클래스에서 virtual 예약어를 붙여 오버라이딩을 하게 해준다.

※오버라이딩 : 부모클래스에 있는 함수를 자식클래스에서 재정의할 때, 함수 호출 시 재정의된 함수를 호출하는 것

class monster{
private:
char name[25];
public:
monster(){ lstrcpy(name,"몬스터");}
virtual void shout(){cout<<name<<"!"<<endl;}
};

class vampire : public monster{
public:
vampire(){ lstrcpy(name,"뱀파이어");}
void shout(){cout<<name<<"피의 혈족이여..."<<endl;}

};
 

이렇게 하고 shout 출력 시에는 "피의 혈족이여..."라는 문장이 출력된다.


 여기서 잠깐, 모든 몬스터의 등장 대사를 이처럼 별개로 하고 싶다고 하자. 그러면 모두 다른 함수를 만들어 개별 호출하는 방법이 있을 것이다. 하지만 완전가상함수를 사용할 경우, 코드는 for문을 돌려 shout만을 출력하는 것으로 메인 코드가 끝날 것이다.

 완전 가상함수라는 것은, 저렇게 '특수한 상황'이 '모든 자식클래스'에 일어날 때 사용한다. virtual 예약어를 붙인 함수의 끝에 = 0;을 붙인다.

ex ) virtual void shout() = 0;

 이렇게 할 경우, 이 부모클래스에서 파생된 모든 자식클래스는 shout함수를 정의해야한다. 빈 코드로라도. 컴파일단계에서 디버깅되기 때문에 실수로 적지 않으면 컴파일에러가 뜬다.



 까지가 상속과 가상함수에 대한 설명. 책을 보지 않고 내가 사용한 내용으로만 적어봤는데, 나중에 책이랑 비교를 해 봐야겠다. 부디 이 글이 상속과 가상함수에 대한 이해에 도움이 되기를.


+) 코드 예제


p.s ) 궁금한 점이나, 오류가 있다면 댓글로 달아주세요.





덧글

  • 컴파일러 머지 2015/03/22 14:13 # 삭제 답글

    제가 쓰는 컴파일러는 화면이 하얀색인데 님은 검은색이네여 어케 하신거죠?
  • 루사인。 2015/03/22 22:54 #

    쓰시는 컴파일러 버전에 따라 답변이 다르며,Visual Studio 2013 기준으로 도구 > 옵션 > 환경 > 일반 > 시각적 효과 - 색 테마 : 어둡게

    하시면 됩니다.
  • 감사합니다 2015/03/25 00:38 # 삭제 답글

    이해하기 쉽게 작성하셨내요! 잘 보고 갑니다 ㅎㅎ
  • 토사물 2015/09/30 20:13 # 삭제 답글

    그냥 토나옵니다 어렵네요
  • .. 2015/12/15 15:08 # 삭제 답글

    감사합니다 도움이 됬어요
  • 1151 2016/10/04 20:49 # 삭제 답글

    게임을 예시로 해줘서 이해가 정말 쉽게되네욬ㅋㅋㅋㅋ 덕분에 이해 잘하고 갑니닼ㅋㅋㅋ
  • ㅋㅋㅋㅋ 2017/05/26 19:33 # 삭제 답글

    ㅋㅋㅋㅋ 정말 재미있게 잘 이해하고 갑니다! 위키에서는 너무 어렵게 설명해놔서 이해하기 어렵더라고요;;
댓글 입력 영역