클래스 상속의 종류 중 public 상속과 private 상속에 대해 알아보자우선 일반적인 클래스 상속이라 하면

부모 클래스의 기능 중 필요한 일부를 하위 클래스가 계승한다. (물려 받는다.) 가 되겠다.

하나의 예를 들어보자상속 모델 개념 설명에 흔하게 사용되는 조류를 예로 들어보겠다.

다음과 같이 Bird라는 클래스가 있다.

  1. class Bird  
  2. {       
  3. public:  
  4.         virtual void fly()=0;  
  5.         virtual void walk()=0;  
  6. protected:  
  7. };  

 

대부분 새는 날 수도 걸을 수도 있다물론 아닌 새도 있다하지만 이럴 때에 대해선 우선 넘어가도록 하자.

다음으로 독수리라는 클래스를 만들려고 한다독수리는 새의 일종이므로 Bird 클래스를 상속받으면 될 것 같다.

  1. class Eagle : public Bird  
  2. {  
  3. public:  
  4.         void fly() {};  
  5.         void work() {};  
  6. };  

 

자 이제 독수리 클래스는 자신만의 나는 방법(fly)과 걷는 방법(work)을 구현할 것이고 객체는 Bird의 일종으로 취급될 것이다.

이처럼 public 상속은 is-a 관계가 만족 돼야 한다 Eagle is Bird 관계가 성립된다코드 상으로 표현하면

  1. Eagle *pEagle = new Eagle();  
  2. Bird *pBird = pEagle;      // 독수리는 새의 일종이므로 가능함.  

 

 

하지만 모든 새는 독수리가 아니므로 반대는 성립되지 않는다. (Bird is not Eagle)

  1. Eagle eagle;  
  2. Bird *pBird = &eagle;     // 독수리는 새의 일종이므로 가능하다.  
  3. Eagle *pEagle = pBird;    // 모든 새는 독수리가 아니므로 불가능하다.  

 

 

이처럼 public 상속은 반드시 is-a 관계가 성립 돼야 한다단순히 부모 클래스의 기능 중 일부가 필요해(is-a가 아니지만) public 상속을 하려 한다면 다른 방법(?)을 생각해 봐야겠다여기서 다시 public 상속을 마무리하기 전에 왜 public 상속이 필요할까?의 좀 더 기초적인 문제로 들어가 보자 Eagle과 위에는 표시되지 않았지만, Duck이라는 클래스도 있을 수 있고 기타 등등의 여러 조류종류가 있겠다이들 각각은 왜 Bird에서 public 상속을 받아야만 할까그냥 각자의 클래스로 구현 돼도 상관없을 텐데… 라는 생각이 든다면 코드의 재활용과 객체 지향적 구현이라고 대답할 수 있겠다위에서 보았듯 부모 클래스의 포인터 즉 Bird *pBird는 모든 종류의 자신의 자식 클래스의 포인터를 받을 수 있다.

  1. pBird = &eagle;  
  2. pBird = &duck;  

 

 

이게 뭐 어떻다는 건가? 라는 의문은 다음의 함수로 없앨 수 있을 것 같다.

(이 함수는 클래스 멤버 함수가 아니며 Bird형의 클래스 객체들을 사용하는 외부에 선언된 함수다.)

  1. // 인자로 입력된 새 객체를 걸은 후 날게 한다.  
  2. void flyAndWolk(Bird *pBird)  
  3. {  
  4.            pBird->walk();    // 걸은 후  
  5.            pBird->fly();       // 난다(물론 duck은 날지 못하지만 여기선 pass)  
  6. }  

 

위 함수는 모든 Bird로부터 public 상속된 객체에 대해 사용 가능하다.

만약 상속되지 않은 Eagle Duck을 위해 flyAndWolk(…)를 구현 해야 한다면?

public 상속의 아주 추상적이고 단편적인 사용 예를 들었지만 실제로는 상속을 받음으로써 구현이 편리해지고 각종 디자인 패턴 등을 적용할 수 있다하지만 위의 예로 public 상속이 어떤 경우에 사용 돼야 하는지 감이 올 것으로 생각한다.

다음으로 private 상속에 대해 알아보자.

 

위에서 public상속을 설명하면서 중간에 다른 방법(?)을 생각해 봐야겠다.”라고 말한 게 있다.

그 경우에 다른 방법을 생각해 봐야 하는 게 바로 private 상속이 되겠다.

우선 private 상속의 개념을 간단히 정리해 보자면 부모(?) 클래스의 public protected 영역이

자식 클래스의 private 멤버가 된다정도로 정리할 수 있을 것 같다.

이게 무슨 의미 일까곰곰히 생각해 보자

  1. class B : private A  
  2. {  
  3. };  
  4. void main()  
  5. {  
  6.     B b;  
  7.     A *pA = &b; // 불가능 하다.  
  8. }  

 

이처럼 public 상속에서 가능하던 게 되지 않으므로 확실히 is-a 관계는 아닌 것으로 보인다.

그럼 무슨 관계냐. Is-implemented-in-terms-of 의 관계가 되겠다즉 부모 클래스의 일부로 자식 클래스를 구현 한다의 개념이 아닌 자식 클래스 내에서 부모 클래스의 기능중 일부를 사용 하겠다 가 되겠다.

그럼 여기서 예를 들어보겠다이번에는 TV와 리모컨의 관계를 사용해 보려 한다.

  1. class Tv  
  2. {  
  3. public:  
  4.         void upChannel(){…};  
  5.         void downChannel(){…};  
  6.   
  7. protected:  
  8.         int m_curChennel;  
  9. };  

 

이번에는 완전한 동작을 하는 Tv부모 클래스다. (public 모델 설명에서 사용된 Bird 같이 virtual 클래스가 아니다이 부분도 일단 pass)

요즘 출시되는 Tv는 모르겠지만, 일반적으로 Tv 본체로 채널을 돌리기 위해선 up/down으로 조절을 해야 한다 1번에서 10번 채널로 바로 갈 수 없다너무 불편하므로 리모컨을 만들어야겠다.

  1. class RemoteCon : private Tv  
  2. {  
  3. public:  
  4.         void changeChennel(int nChennel);  
  5. };  

 

Private 상속을 받았다이제 private 상속의 정의에 나왔듯 Tv클래스의 두 멤버함수 upChannel, downChannel과 멤버변수 m_curChennel는 내 것이 된다.

Public 상속을 받아도 똑같지 않느냐? 라는 의문이 드는 사람은 is-a 관계를 다시 한번 떠올려 보자.

그럼 이제 답답하게 채널을 한 칸 위, 한 칸 아래로 조절하는 게 아닌 한번에 원하는 채널로 이동할 수 있는 리모컨의 changeChennel() 함수를 구현 해보자.

  1. void RemoteCon::changeChennel(int nChennel)  
  2. {  
  3.         while(m_curChennel != nChennel)  
  4.         {  
  5.                if(m_curChennel < nChennel)  
  6.                        upChannel();  
  7.                else  
  8.                        downChannel();  
  9.         }  
  10. }  

 

대충이런 모양이 될 듯(?)하다지금은 구현 부가 중요한 건 아니니 그냥 넘어가겠다m_curChennel에 바로 nChennel를 대입하지 않은 이유는 내부적으로 채널 변경에 핵심 동작이 upChannel() 과 downChannel() 내에 구현되 있다는 가정하에 구현됐기 때문이다.

이처럼 Tv 클래스를 이용해 리모컨 객체를 구현하게 됐다.

하지만 대부분 private 상속은 심사숙고를 해본 후 사용하는 게 좋다.

이유는 아래와 같은 몇 가지 단점이 존재하기 때문이다.

 

1. 하나의 객체가 다수 객체를 포함하는 개념을 표현해야 할 경우

위 TV와 리모컨의 예에서 리모컨이 두 이상의 TV를 지원 한다고 가정할 동일 클래스를 두 상속받을 수 없으므로 표현할 수 가 없다.

만약 이를 위해 TV2라는 클래스를 만들어 TV와 TV2를 다중 private상속 하도록 한다면 마찬가지로 TV2의 부모가 TV이므로 인터페이스가 충돌하여 이런 문제를 피할 수 없다.

 

2. 하위 객채를 더 확장시킬 때

마찬가지로 위 TV와 리모컨의 예에서 RemoteCon객체의 기능 확장을 위해 이를 부모로 RemoteCon2를 생성했다고 해보자.

이 경우 RemoteCon2는 TV클래스의 protected멤버와 심지어 public멤어 또한 사용할 수 없게 된다. private 상속의 특성상 해당 멤버들은 이미 RemoteCon클래스가 상속하는 과정에서 RemoteCon클래스의 private멤버로 가려졌기 때문이다.

 

하지만 Is-implemented-in-terms-of라는 다소 복잡해 보이는 관계가 대부분 has-a라는 방법으로 구현할 수 있다.

또한 이 방법을 사용할 경위 위의 문제점 들을 간단하게 해결 할 수 있다.

 

 Remocon 클래스에서 멤버 변수로 Tv객체 하나를 갖게 한다.(상속이 아니다.)

내부에서 changeChennel()함수를 구현하기 위해서는 Tv클래스의 두 public 함수인

  1. void upChannel();  
  2. void downChannel();  

 

 

를 이용한면 된다.

구현은 다음과 같이 바뀔 것이다.

  1. class RemoteCon  
  2. {  
  3. public:  
  4.         void changeChennel(int nChennel);  
  5.   
  6. private:  
  7.   
  8.         m_Tv;   // tv 객체  
  9. };  
  10.   
  11. void RemoteCon::changeChennel(int nChennel)  
  12. {  
  13.         while(m_Tv.getCurChennel() != nChennel)  
  14.         {  
  15.                if(m_Tv.getCurChennel()  < nChennel)  
  16.                        m_Tv.upChannel();  
  17.                else  
  18.                        m_Tv.downChannel();  
  19.         }  
  20. }  

 

위와 이 상속 개념이 아닌 소유의 개념이므로 객체를 통해서 함수가 호출되도록 변경됐다.

또한,  비 상속 관계에서는 protecte또는 private 영역에 접근이 불가능하므로 현재 채널 정보를 가진 m_curChennel 변수를 대신할 int getCurChennel() 함수가 Tv클래스 쪽에서 구현이 돼야 한다.

 

Private 상속과 has-a 관계 외에도 비슷한 목적을 위한 기법이 몇 개 있지만 private상속에 대한 설명은 여기까지 충분한 것으로 보인다이처럼 상속구조보다는 has-a관계로 객체 형식으로 사용하는 게 내부에서 구현을 할 때에도 이 함수는 어떤 어느 객체의 함수인지가 명확해지므로 코드 가독성이 높아지는 효과도 있다.

 

+ Recent posts