Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

로또

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

책/Effective C++

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

아롱로또 2023. 12. 1. 01:06

다루는 내용

클래스들 사이에 맺을 수 있는 관계들 (is-a, has-a, is-implemented-in-terms-of) 중 is-a의 의미를 가지는 public 상속에 대해 알아보자.

모든 Derived는 Base의 일종이다.

클래스 D(Derived)를 클래스 B(Base)로부터 public 상속을 통해 파생시켰다면, D타입의 모든 객체는 또한 B타입의 객체이지만 그 반대는 성립하지 않는다. 즉, B타입의 객체가 쓰이는 곳에 D타입의 객체도 마찬가지로 쓰일 수 있지만 D타입의 객체가 필요한 부분에 B타입의 객체는 쓰일 수 없다.

class Person{ ... };
class Student public Person { ... }; // public 상속 - 모든 학생은 사람이다.

void eat(const Person& p);
void study(const Student& s);

int main(){
	Person p;
	Student s;
	eat(p);
	eat(s); // 가능, 모든 학생은 사람이다.
	study(p); // 불가능, 모든 사람은 학생이 아니다.
	study(s);
}

 

모든 새는 날 수 있다?

"public 상속 == is-a 관계" 라는 이야기는 직관적이지만 판단을 잘못하는 경우도 존재한다.

'새' 라는 개념을 보았을 때, 새는 날 수 있고, 펭귄은 새의 일종이다. 때문에 다음과 같이 코드를 작성하여 표현할 수 있을 것이다.

class Bird{
public:
	virtual void fly();
};

class Penguin: public Bird{ ... };

...
Penguin pg
pg.fly(); // 펭귄이 난다.

펭귄은 실제로 날 수 없음에도, 펭귄은 fly 함수를 통해 날 수 있다. 하지만 이는 맞지 않다.

'새는 날 수 있다.' 가 '모든 새는 날 수 있다.' 라는 의미를 가진 것은 아니기 때문이다.

새도 날 수 있는 새가 있고, 날지 못하는 새가 있을 것이다. 그러니 다음과 같이 코드를 수정할 수 있다.

class Bird{...};

class FlyingBird: public Bird{
public:
	virtual void Fly();
};

class Penguin: public Bird{...};

이러한 방식은 Penguin 객체에 Fly 함수를 호출하는 일이 있더라도 컴파일 단계에서 에러를 발생시켜줄 것이다.

 

모든 정사각형은 직사각형이다.

모든 새가 날 수 있다는 명제는 펭귄으로 인해 틀렸지만, 모든 정사각형은 직사각형이라는 것은 많은 사람들이 알고 있는 사실이다. 모든 정사각형은 직사각형인데, 직사각형은 정사각형이 아닐 수도 있으니 상속 관계를 만들기에 정말 좋아보인다.

class Rectangle{
public:
	virtual void SetHeight(int new_height);
	virtual void SetWidth(int new_width);
	virtual int GetHeight() const;
	virtual int GetWidth() const;
	...
};

void MakeBigger(Rectangle& r){
	int old_height = r.GetHeight(); // 현재 세로 길이
	r.SetWidth(r.GetWidth() + 10); // 가로 길이 + 10
	assert(r.GetHeight() == old_height); // r의 세로 길이는 변하지 않음을 확인
}

MakeBigger 함수는 Rectangle 객체를 받아 가로 길이를 10만큼 늘려주는 역할을 한다.

가로 길이를 늘린 후, 세로 길이를 비교하여 세로 길이가 변하면 문제가 있다고 판단하는 단정문이 존재한다.

이제, Rectangle 클래스를 상속한 Square 클래스를 만들고, MakeBigger 함수를 사용해보자.

class Square: public Rectangle{...};

int main(){
	Square s;
	assert(s.GetWidth() == s.GetHeight()); // 모든 정사각형의 가로와 세로의 길이는 같다.
	MakeBigger(s); // 가로 길이를 10 늘린다.
	assert(s.GetWidth() == s.GetHeight());
}

무언가 이상한 점이 보인다.

  • MakeBigger 함수를 호출하기 전에, s의 세로 길이는 가로 길이와 같아야 한다.
  • MakeBigger 함수가 실행되는 중에, s의 가로 길이는 변하지만 세로 길이는 유지되어야 한다.
  • MakeBigger 함수 호출이 끝난 후, s의 세로 길이는 가로 길이와 같아야 한다.

모든 정사각형은 직사각형이지만 위와 같은 코드에서는 분명 단정문에 의해 코드가 종료되고 말 것이다.

public 상속은 기본 클래스 객체가 가진 모든 것들이 파생 클래스 객체에도 그대로 적용된다고 단정한다.

 

이것만은 잊지 말자!

  • public 상속의 의미는 "is-a(...는 ...의 일종)"이다. 기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되어야 한다. 왜냐하면 모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문이다.