언어/Effective C++

[Effective C++] #32_public 상속

Hardii2 2022. 1. 3. 01:19

[Effective C++] #32_Public 상속

 

Scott Meyers의 "Effective C++" 를 통해, C++ 구현에 필요한 개념들을 이해하고, 기록하기 위함입니다. 해당 항목은 6장 '상속 그리고 객체 지향 설계', 항목 32 "Public 상속 모델은 반드시 ~는~의 일종이다"에 해당하는 내용입니다.

 

 


 

Public 상속의 의미
class Person {...};

class Student: public Person {...};

"Public 상속은 is-a(...는...의 일종이다)" 클래스 B를 클래스 A로부터 public 상속을 통해 파생시켰다면, B 타입으로 만들어진 모든 객체는 또한 A 타입의 객체이지만, A 타입으로 생성된 모든 객체는 B 타입의 객체가 아닙니다! 쌍방 관계가 성립될 수 없죠. 따라서, A는 B보다 더 일반적인 개념이며, B는 A보다 더 특수한 개념을 나타냅니다. 쉽게 예를 들면, 모든 학생은(B 타입) 모두 사람(A 타입)이지만, 모든 사람은(A 타입) 학생(B타입)이 아닙니다!

 

1. Public 상속 예제: 새 그리고 펭귄
class Bird {
public:
    virtual void fly;
    ...
};

class Penguin: public Bird {
    ...
}

새는 날 수 있습니다. 펭귄은 새의 일종입니다.  하지만, 위 코드 내용을 살펴보면, '모든 새는 날 수 있다, 그래서 새의 일종인 '펭귄' 또한 날 수 있다'로 해석됩니다. 하지만, 사실은 그렇지 않죠. 코드를 수정해 보겠습니다.

 

void error(const std::string&  msg);

class Penguin: public Bird {
public:
   virtual void fly() {error ("Attempt to make a penguin fly!");}
    ...
};

위 코드의 경우, "펭귄은 날 수 없다"로 해석되기 보다, "펭귄은 날 수 있다", 그러나 펭귄이 실제로 날려고 하면 에러가 난다"로 보입니다. 이러한 규칙 위반은 프로그램 실행 중에 발견 할 수 있는 에러로, 유효하지 않은 코드를 컴파일 단계에서 막아 줄 수 없겠죠. 따라서 아래처럼 수정합니다.

 

class Bird {
    ...		//fly 함수가 없습니다.
};

class Penguin: public Bird {
    ...		//역시 fly 함수가 없습니다
};

Penguin p;

p.fly(); 	//에러 발생!!

위 코드처럼 수정한 결과는, 컴파일 단계에서 유효하지 않은 코드를 막아 줄 수 있게됩니다.

 

1. Public 상속 예제: 직사각형 그리고 정사각형
class Rectangle {
public:
    virtual void setHeight(int newHeight);
    virtual void setWidth(int newWidth);
    
    virtual int hegith () const;
    virtual int width () const;
    ...
};

void makeBigger(Rectangle& r){
    int oldHeight = r.height();	
    
    r.setWidth(r.width() + 10);		//가로 길이에 10을 더합니다. 
    
    assert(r.height() == oldHeight);	//직사각형 r의 세로길이는 절대 안변합니다.
}

class Square: public Rectangle {...};	// 직사각형 클래스를 상속받은 정사각형 클래스

Square s;
...
assert(s.width() == s.height());	//해당 조건문에 무조건 참이어야 합니다.

makeBigger(s);				//makeBigger 함수를 통해 s의 넓이를 넓힙니다.

assert(s.width() == s.height());	//해당 조건문이 무조건 참이어야 합니다.

위 코드에서 "makeBigger" 함수는 "r"객체의 가로 길이만 변경합니다. 아래 assert를 이용한 조건문 2개는 무슨 일이 있어도 참이어합니다. 더불어, "직사각형" 클래스를 상속받은 "정사각형" 클래스는 "makeBigger" 함수를 호출 할 수 있습니다. makeBigger 함수는 Rectangle 객체에 대한 참조자를 인자로 받기 때문이죠. 하지만, 말이 안되죠. 정사각형은 넓이 혹은 높이 중 한쪽만 길어져도, 더이상 정사각형이 아닌 직사각형이 되버리기 때문입니다. Public 상속은 우리가 배웠던 "정사각형 -> 직사각형, 정사각형은 직사각형의 일종이다"의 개념이 성립되지 않습니다. 따라서, 정사각형과 직사각형의 관계를 Public 상속을 써서 표현하면 틀립니다.

 

Public 상속의 의미는 기본 클래스 객체가 가진 모든것들이 파생 클래스 객체에도 그대로 적용된다고 단정 짓는다.