- public 상속의 의미는 is-a이다. 기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되어야 한다. 왜냐하면 모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문이다.
public 상속 모형은 반드시 is-a
public 상속은 is-a이다. 이 사실을 무조건 기억해야 한다.
B(Base) 클래스로부터 D(Derived) 클래스를 파생시켰다면, D 타입으로 만들어진 모든 객체는 B타입의 객체이지만, 그 반대는 되지 않는다.
다시 말해 B는 D보다 더 일반적인 개념을 나타내며, D는 B보다 더 특수한 개념을 나타낸다.
B타입의 객체가 쓰일 수 있는 곳에는 D타입의 객체가 쓰일 수 있다. D타입의 모든 객체는 B타입의 객체도 되기 때문이다.
C++은 public 상속의 문법은 다음과 같다.
class Person { ... };
class Student : public Person { ... };
Person 타입의 인자가 필요한 함수는 Student 객체도 받아들일 수 있다.
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
이 이야기는 public 상속에서만 해당된다. private 상속은 의미 자체가 완전히 다르다.
public 상속과 is-a 관계가 똑같은 뜻이라는 이야기는 직관적이지만 잘못 적용되는 경우도 있다.
예를 들면, 펭귄이 새의 일종이다. 새라는 개념만 보면 새가 날 수 있는 것은 맞지만 펭귄을 날지 못한다.
class Bird {
public:
virtual void fly();
...
};
class penguin: public Bird {
...
};
이렇게 되면 날지 못하는 펭귄도 fly를 쓸 수 있게 된 것이다.
다음과 같이 설계해야 말이 되는 설계가 된다.
class Bird {
...
};
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!"); }
...
};
컴파일이 되긴 하지만, 프로그램이 실행될 때 에러를 발견할 수 있다.
물론 문제를 컴파일 단계에서 발견하는 것이 좋은 인터페이스이다. 그러니 fly가 꼭 필요 없는 함수라면 아예 제거하는 것이 좋다.
또 다른 예를 보자.
정사각형은 직사각형이 될 수 있다.
class Rectangle {
public:
virtual void setHeight(int newHeight);
virtual void setwidth(int newWidth);
virtual int height() const;
virtual int width() const;
...
};
void makeBigger(Rectanlge& r)
{
int oldHeight = r.height();
r.setWidth(r.width() + 10);
assert(r.height() == oldHeight);
}
makeBigger은 r의 가로길이만 변경할 뿐이고, 세로길이는 바뀌지 않는다.
class Square: public Rectangle { ... };
Square s;
...
assert(s.width() == s.height());
makeBigger(s);
assert(s.width() == s.height());
하지만, 직사각형의 성질이 정사각형에 적용되면 안 되는 것들이 있다.
makeBigger 함수를 호출하기 전에, s의 세로 길이와 가로길이는 같다.
makeBigger 함수가 실행되면 s의 가로길이는 변하는데 세로길이는 변하지 않는다.
mkeBigger 함수가 복귀한 후, s의 세로길이는 역시 가로길이와 같아야 한다.
가로길이만 길어졌기 때문에 더이상 s는 정사각형이 아니게 된다.
그러나 public 상속은 기본 클래스 객체가 가진 모든 것들이 파생 클래스 객체에도 그대로 적용되게 된다.
따라서 이 둘의 관계를 public 상속을 써서 표현하려고 하면 틀리게 되는 것이다.
하지만 컴파일은 문제없이 일어난다. 그러니 이 부분을 유의하여 설계해야 한다.