방프리

17.12.12. Effective C++ 6. 상속, 그리고 객체 지향 설계 (항목32) 본문

C++/Effective C++

17.12.12. Effective C++ 6. 상속, 그리고 객체 지향 설계 (항목32)

방프리 2020. 1. 17. 01:02

항목 32 : public 상속 모형은 반드시 "is-a(...는 ...의 일종이다)"를 따르도록 만들자

 

객체 지향 OOP의 3대 요소 중 하나인 상속!! 짚고 넘어가지 않을 수 없는데요.

책에서도 다른 건 다 까먹어도 되지만 이 항목만큼은 항상 기억하고 넘어가야 한다고

강조하고 있습니다. 그만큼 중요하다는 뜻이겠죠?

일반적으로 public 상속은 "...는 ...의 일종이다~" 라고 정의할 때 사용합니다.

이해가 잘 안되시죠? 차근차근 예를 들어서 살펴보겠습니다.

간단한 몬스터를 생성하는 클래스를 통해 알아보겠습니다. 

 

ex1) 

class cMonster

{

//do Something

};//몬스터 모두를 지징하는 클래스

 

class cGoblin : public cMonster

{

//do Something

};// 고블린 몬스터를 지칭하는 클래스

 

몬스터는 고블린이 될 수 있습니다. 하지만 고블린은 몬스터가 될 수 없죠 무슨 말인지 잘 모르겠다구요?

몬스터를 지칭하면 고블린도 몬스터이기 때문에 고블린은 몬스터의 일종이다~ 라고 할 수 있습니다.

하지만 몬스터는 고블린의 일종이다~ 이상하지 않나요? 고블린보다 몬스터가 더 큰 개념이기 때문이죠

그런고로 책에서 다음과 같이 정리하였습니다.

(1) D(Derived) 타입으로 만들어진 모든 객체는 또한 B(Base) 타입의 객체이지만, 그 반대는 되지 않음

(2) B 타입의 객체가 쓰일 수 있는 곳에는 D 타입의 객체도 마찬가지로 쓰일 수 있다고 단정

(3) D 타입이 필요한 부분에 B 타입의 객체를 쓰는 것은 불가능

(4) 모든 D는 B의 일종이지만, B는 D의 일종이 아님

(5) C++는 public 상속을 이렇게 해석하도록 문법적으로 지원

But.... 항상 예외는 존재하는 법...(괜히 예외처리가 있는게 아니죠~)

예를 보면서 설명드리겠습니다. 일단 새에 관한 클래스를 만들어보겠습니다.

ex2)

class cBird

{

public:

virtual void Fly(); //새는 날 수 있으니깐

};

그리고 타조에 관한 클래스를 만들어보겠습니다.

ex3)

class cOstrich : public cBird

{

public:

void Fly(); //무슨 소리 입니까? 타조는 못날아요!!

};

 

네.... 이딴 거지같은 함정이 생겨버리고 맙니다. (왜 상속을 했는데 쓰질 못하니...ㅜㅜ)

결국 몇 가지 클래스를 더 거쳐 만들 수 밖에 없죠...

ex4)

class cFlyingBird : public cBird

{

public:

virtual void Fly();

//do Something

};

class cOstrich : public cFlyingBird 

{

//do Something

};

다른 방법도 존재하긴 합니다. Fly 함수를 재정의해서 런타임 에러를 발생시키는 것이죠

필자는 여기서 끝나지 않고 하나의 예를 더 사용하여 우리에게 경고를 보여줍니다.

Sqare(정사각형) 클래스는 Rectangle(직사각형) 클래스로부터 상속을 받아야 하는가?

ex5)

class cRectangle

{

public:

virtual void setHeight(int newHeight);

virtual void setWidth(int newWidth);

virtual int height() const;

virtual int width() const;

};

 

void makeBigger(Rectangle& r) // 사각형의 넓이를 늘리는 함수

{

int oldHeight = r.height();

r.setWidth(r.width() + 10 ); //r의 가로길이에 10을 더함

assert(r.height() == oldHeight); //r의 세로 길이가 변하지 않는다는 조건에 assert를 걸어둠

}

 

class cSquare : public cRectangle()

{

//do Something

};

 

int main(void)

{

Square s;

assert(s.width() == s.height()); //모든 정사각형은 모든 변의 길이가 같아야겠죠?

makeBigger(s); //사각형의 넓이를 늘립니다. 정사각형이요!!

assert(s.width() == s.height()); //마찬가지로 조건을 걸어둡니다.

}

 

와....극혐이네요... 직사각형의 속성을 받으니 정사각형의 역할을 제대로 하질 못합니다. 

필자는 이 예를 보여주면서 연관되지 않은 속성끼리 억지로 연관시켰다가는 이런 참사를 

불어온다는 것을 몸소 보여줍니다. 가장 많이 사용하는 프로그래밍 기법으로써 꼭꼭!!

알고 넘어가야할 것 같습니다. 

(참고로 이런 상속 기법은 메모리 낭비와 구조 설계에 굉장히 복잡하기 때문에 지금은 거의 사용되지 

않습니다. 그럼 현재 뭐가 쓰이냐? 유니티나 언리얼 엔진만 봐도 답이 나오지 않나요?)

 

이것만은 잊지 말자!

 

* public 상속의 의미는 "is-a(...는 ...의 일종)"입니다. 기본 클래스에 적용되는 모든 것들이 파생 클래스에

그대로 적용되어야 합니다. 왜냐하면 모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문입니다.

Comments