C++에서는 public 상속을 "is a" 관계에 있는 것으로 해석하고 있기 때문에 상속을 설계할 때 "is-a" 관계에 해당하는지를 반드시 확인해야 한다.
상속 관계 "is-a"
class Person{...};
class Student: public Person{...};
void eat(const Person& p);
void study(const Student& s);
Person p;
Student s;
eat(p); // ok
eat(s); // ok
study(s); //ok
study(p); // error
"Student는 Person 이다" 는 되지만 그 반대인 "Person 은 Student 이다" 는 되지 않는다.
즉, Person이 쓰이는 곳에는 Student가 대체될 수 있지만 Student가 쓰이는 곳에는 Person이 대체될 수 없다.
위 예제에서 eat(s)는 되지만 study(p)는 될 수 없는 것이다.
상속 설계 방식은 소프트웨어 시스템에 따라 다르게 정의될 수 있다.
class Bird{
public:
virtual void fly();
...
};
class penguin: public Bird{
...
};
펭귄은 날개가 달려 있기 때문에 fly() 할 수 있는 Bird() 를 상속받아 정의할 수 있다.
하지만 어떤 소프트웨어에서는 날 수 있는 새와 날수 없는 새를 구분해서 알고리즘의 차이가 생길 수 있기 때문에 위 상속관계보다는 FlyingBird를 따로 만들어 상속관계를 표현하는 것이 더 좋을 수 있다.
class Bird{
public:
...
};
class FlyingBird: public Bird{
public:
virtual void fly();
...
};
class penguin: public Bird{
...
};
이렇게 표현되면 펭귄은 새이지만 날지 않는 새이고, 날지 않은 새에만 적용되는 동작을 쉽게 처리할 수 있게 된다.
즉, 소프트웨어 시스템에서 기대하는 바에 따라 달라지는 것이고 반드시 어떤 방식으로 상속관계를 정의해야한다 라고 하는 부분은 없는 것이다.
소프트웨어 시스템에 따라 다르지만 설계단계에서 한가지 더 좋은 방식은 컴파일 단계에서 에러를 확인할 수 있도록 만드는게 중요하다.
만약 날지 않는 새에 대해 fly() 동작을 하게 되면 에러를 발생하게 만든다고 했을 때 아래 방식처럼 런타임에 에러를 내는 방식은 추천하지 않는다.
void error(const std::string& msg);
class Penguin: public Bird{
public:
virtual void fly(){ error("Attempt to make a penguin fly!"); }
FlyingBird와 Bird를 구분하는 함수를 만들고 펭귄이 FlyingBird 가 아닌 Bird를 상속받게 하는 것이 더 좋다.
Bird에는 fly() 함수가 없기 때문에 fly()를 사용하게 되면 컴파일단에서 에러를 발생시키기 때문이다.
public 상속은 기본 클래스 객체가 가진 모든 것들이 파생 클래스 객체에도 그대로 적용된다고 단정하는 상속이다.
정사각형은 직사각형의 일부분에 속한다.
직사각형을 상속하여 정사각형을 만들 수 있을까?
NO, 직사각형의 모든 동작을 정사각형에서도 적용시킬 수 있어야 하는데 그렇지 않기 때문이다.
class Rectangle{
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);
assert(r.height() == oldHeight);
}
세로 길이가 변하지 않는다는 것을 makeBigger() 함수에 추가해 놓으면 makeBigger의 매개변수로 정사각형이 들어갔을 때 원하는 결과를 얻기는 힘들다. 정사각형의 정의에 벗어나는 결과가 나오기 때문이다.
"has-a" 관계 또는 "is implemented in terms of" 관계와 헷갈리지 말자.
'Effective C++' 카테고리의 다른 글
이펙티브(Effective) C++ item 34 : 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자 (0) | 2022.06.26 |
---|---|
이펙티브(Effective) C++ item 33 : 상속된 이름을 숨기는 일은 피하자 (0) | 2022.06.26 |
이펙티브(Effective) C++ item 31 : 파일 사이의 컴파일 의존성을 최대로 줄이자 (0) | 2022.06.26 |
이펙티브(Effective) C++ item 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자. (0) | 2022.06.19 |
이펙티브(Effective) C++ item29 : 예외 안전성(Exception safety)이 확보되는 그날 위해 싸우고 또 싸우자! (0) | 2022.06.19 |
댓글