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

Jangmanbo·2024년 4월 17일
0

Effective C++

목록 보기
32/33

Derived 클래스를 Base 클래스로부터 public 상속을 통해 파생시켰다.
= Derived 객체는 Base 객체이다.

즉, 상대적으로 Base는 일반적인 개념이며, Derived는 특수한 개념이다.
따라서 Base타입을 쓰는 객체 대신에 Derived객체를 사용할 수 있다.



올바른 public 상속

void eat(const Person& p);			// 사람은 먹는다
void study(const Student& a);		// 학과 공부는 학생만 한다

Person p;
Student s;

eat(p);								// Success.
eat(s);								// Success.

study(s);							// Success.
study(p);							// Error. p는 Student가 아니다


잘못된 public 상속 예제 1

class Bird
{
public:
	virtual void fly();			// 새는 날 수 있다
    ...
};

class Penguin : public Bird		// 펭귄은 새다
{
	...
};

is-a 관계에만 집중해서, 펭귄은 새의 일종이므로 PenguinBird를 public 상속했다.
그런데 펭귄은 날 수 없다.

해결법1: 클래스 계통 구조 변경

class Bird
{
    ...							// fly 함수 선언 X
};

class FlyingBird : public Bird
{
public:
	virtual void fly();			// fly 함수 선언 O
    ...							
};

class Penguin : public Bird
{
	...							// fly 함수 선언 X
};

날 수 있는 새와 날지 못하는 새를 구분하도록 클래스를 설계해서 해결할 수 있다.

다만 이런 설계는 이 소프트웨어 시스템이 새의 비행 능력에 관심이 있을 때나 의미가 있다.
만약 새의 부리와 날개에만 관심이 있는 시스템이라면 굳이 비행 능력에 따라 구분할 필요가 없다.

최고의 설계는 제작하려는 소프트웨어 시스템이 기대하는 바에 따라 달라진다!

해결법2: 에러 발생시키기

런타임 에러

void error(string& msg);

class Penguin : public Bird
{
public:
	virtual void fly() { error("펭귄은 못날아!"); }
    ...
};

펭귄이 날면 런타임 에러가 나게 만들었다.

이 코드의 의미는 "펭귄은 날 수 있지만 펭귄이 실제로 날려고 하면 에러가 난다" 이다.
사용자가 바랬을 "펭귄은 날 수 없다"가 아니다.

컴파일 에러

class Bird
{
	...							// fly 함수 선언 X
};

class Penguin : public Bird
{
	...							// fly 함수 선언 X
};

Penguin p;

p.fly();						// Error.

유효하지 않은 코드는 컴파일 단계에서 막아주는 인터페이스가 좋은 인터페이스다.
즉, 런타임보다 컴파일에서 에러가 발생하도록 하는 설계가 더욱 좋다. 항목 18



잘못된 public 상속 예제 2

// 직사각형
class Rectangle
{
public:
	virtual void setHeight(int newHeight);
    virtual void setHeight(int newHeight);
    
    virtual int height() const;
    virtual int width() const;
    ...
};

// 정사각형
class Square : public Rectangle { ... };

// 직사각형의 가로 길이만 변경하는 함수
void makeBigger(Rectangle& r)
{
	int oldHeight = r.height();
    
    r.setWidth(r.width() + 10);
    
    assert(r.height() == oldHeight);
}


Square s;
...
assert(s.width() == s.height());

makeBigger(s);

assert(s.width() == s.height());

makeBigger함수를 호출한 이후 s의 가로 길이가 늘어난다.
따라서 두 번째 assert문이 실패한다.

이런 경우 애초에 직사각형-정사각형 관계를 public 상속을 써서 표현하면 안된다.
"가로 길이가 세로 길이에 상관없이 바뀔 수 있다"는 직사각형의 성질이 "가로와 세로 길이가 같아야 한다"는 정사각형에 적용할 수 없기 때문이다.





public 상속

  • is-a(...는 ...의 일종)이라는 의미
  • 기본 클래스에 적용되는 모든 것들이 파생 클래스에 그대로 적용되어야 함

0개의 댓글