본문 바로가기

프로그래밍 언어들/C++

CHAPTER07 - 상속(inheritance)의 이해

1. 상속(Inheritance)

상속은 특히 적용이 중요한 문법이다. 적절한 때에 선별적으로 적용할 수 있어야 한다.


시작에 앞서, '컨트롤(control)' 클래스 또는 '핸들러(handler)' 클래스에 대해 알아보자.

- 기능의 처리를 실제로 담당하는 클래스로, 기능 제공의 핵심이 되기 때문에 반드시 존재하는 클래스이다.


(1) 상속이란?

B 클래스가 A 클래스를 상속하게 되면, A클래스가 지니고 있는 모든 멤버를 물려받는다. 즉, B 클래스에 선언되어

있는 멤버들 뿐만 아니라, A 클래스의 멤버들도 지니게 된다.

- 상속받은 클래스는 자신에게 상속한 클래스의 멤버를 초기화할 의무를 지닌다.

- 따라서 상속한 클래스의 생성자를 통해 멤버를 초기화하는 것이 좋다.

class A
{
private:
    int num;
public:
    explicit A(const int &n) : num(n) {}
    void show(void)
    {
        cout << num << endl;
    }
};

class B : public A
{
private:
    char ch;
public:
    explicit B(const char &c, const int &n) : A(n)
    {
        ch  = c;
    }
    void showall(void)
    {
        show();
        cout << ch << endl;
    }
};
int main( void)
{
    B b('A', 50);

    b.showall();

    return 0;
}
50
A


위의 예제에서 보이는 것과 같이 'B' 클래스는 'A' 클래스를 상속했다. 그렇기 때문에

A 클래스의 함수인 'show()'를 호출할 수 있다. 또한 앞서 설명한 것처럼

"B 클래스는 A 클래스를 초기화해야 할 의무가 있다." 따라서 이니셜라이저를 이용하여,

A 클래스의 생성자를 호출해 초기화를 진행한다.


위의 예제를 보면서 다음의 내용이 궁금할 수가 있다.

"B 클래스에서 A 클래스에 'private'으로 선언된 멤버에 접근이 가능한가.?"

객체를 기준으로 보면 가능하다고 해야 옳지만, 접근제한의 기준은 클래스이다. 따라서 상속 관계 일지어도

직접 접근이 불가능하다. 따라서 상속한 클래스의 'public' 함수를 통해서 간접적으로 접근해야 한다.


(2) 용어의 정리

상위 클래스

하위 클래스 

 기초(base) 클래스

 유도(derived) 클래스

 슈퍼(super) 클래스

 서브(sub) 클래스

 부모 클래스

 자식 클래스


이 중에서 '기초 클래스'와 '유도 클래스'라는 C++에서 가장 일반적으로 사용되는 표현을 주로 사용하겠다.


(3) 유도 클래스의 객체 생성과정

유도 클래스의 객체 생성과정은 매우 중요하기 때문에 조금 더 정확히 이해하고 넘어가고자 한다.

class A
{
private:
    int basenum;
public:
    explicit A(void) : basenum(10) {}
    explicit A(const int &n) : basenum(n) {}
    void showbase(void)
    {
        cout << basenum << endl;
    }
};

class B : public A
{
private:
    int dnum;
public:
    explicit B(void) : dnum(20) {}
    explicit B(const int &n) : dnum(n) {}
    explicit B(const int &n, const int &m) : A(n), dnum(m) {}
    void showall(void)
    {
        showbase();
        cout << dnum << endl;
    }
};

int main( void)
{
    cout << "case 1" << endl;
    B b1;
    b1.showall();

    cout << "case 2" << endl;
    B b2(30);
    b2.showall();

    cout << "case 3" << endl;
    B b3(50, 50);
    b3.showall();

    return 0;
}
case 1
10
20
case 2
10
30
case 3
50
50


소스코드와 실행결과를 주의 깊게 살펴보면, 다음 두 가지 사실을 알 수 있다.

- 유도 클래스의 객체생성 과정에서 기초 클래스의 생성자는 100% 호출된다.

- 유도 클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다.

즉, 유도 클래스의 객체생성 과정에서는 생성자가 두 번 호출된다. 유도 클래스의 생성자와 기초 클래스의 생성자이다.

※ 유도 클래스의 객체가 할당되고 초기화되는 과정에서, 무조건 기초 클래스의 생성자가 먼저 수행된다.

※ 유도 클래스의 소멸자도 마찬가지로 기초 클래스의 소멸자를 포함하여 두 번 호출된다.

그러나 소멸자의 경우에는 유도 클래스 -> 기초 클래스 순으로 호출된다.


2. protected 선언과 세 가지 형태의 상속

C++의 접근제어 지시자에는 private, protected, public 이렇게 세 가지가 존재한다.

그리고 이들이 허용하는 접근의 범위는 다음의 관계가 있다.

private < protected < public

즉, 'protected'로 선언된 멤버는 유도 클래스에서 직접 접근이 가능하다. 유도 클래스에게만

제한적으로 접근을 허용한다는 측면에서 유용할 수도 있지만, 기본적으로 기초 클래스와 유도 클래스 사이에도

'정보은닉'은 지켜지는게 좋다.


(1) 세 가지 형태의 상속

상속은 다음과 같은 세 가지 형태로 가능하다.

class B : public A {};
class B : protected A {};
class B : private A {};


각각 의미는. "상속되는 키워드보다 접근의 범위가 넓은 멤버는 상속 키워드로 변경시켜 상속하겠다."이다.

즉, 'protected'로 상속할 경우, 기초 클래스의 멤버 중 public으로 선언된 멤버들은 모두 'protected'로 변경되어

상속이 진행 된다.

class A
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
};

class B : protected A {};

int main( void)
{
    B b;

    cout << b.num3 << endl; // occur the compile error

    return 0;
}


위의 예제에서 보면, 클래스 A가 'protected'로 상속 되었다. 즉 클래스의 A의 멤버 중 'public'으로 선언된

'num3'는 protected로 변한되어 상속되기 때문에, 클래스 외부에서 직접 접근이 불가능하다.

protected 상속 전

상속 후 

class A
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
};
class B
{
접근불가:
    int num1;
protected:
    int num2;
protected:
    int num3;
};


num1이 'private'에서 '접근불가'가 된 이유는, 만약 num1이 'private'라면 B 클래스에서 접근이 가능해야

할 것 아닌가? 하지만 기초 클래스 외에는 접근이 불가능하므로 위와 같이 표현한다.


다음으로 'private 상속'도 위의 원리와 같다.

private 상속 전

상속 후 

class A
{
private:
    int num1;
protected:
    int num2;
public:
    int num3;
};
class B
{
접근불가:
    int num1;
private:
    int num2;
private:
    int num3;
};


'public 상속'은 "private을 제외한 나머지는 그냥 그대로 상속한다."라는 의미이므로, 따로 다루지 않겠다.

실제로 public 이외의 상속은 다중상속과 같이 특별한 경우가 아니면 잘 사용하지 않는다.


3. 상속을 위한 조건

상속으로 클래스의 관계를 구성하기 위해서는 조건이 필요하다. 그리고 그 조건과 피룡가 충족되지 않으면,

상속은 하지 않는 것만 못하다고 전문가들은 이야기 한다.


(1) 상속을 위한 기본 조건인 IS-A 관계의 성립

상속의 기본 문법에서 보이듯이, 유도 클래스는 기초 클래스의 특징을 포함하면서, 유도 클래스만의 추가적인

특성이 더해진다. 그렇다면 현실 세계에서는 이러한 상황이 언제 연출될까?

- 전화기 => 무선 전화기

- 컴퓨터 => 노트북 컴퓨터

즉, 전화기와 컴퓨터의 원래 목적은 '통화'와 '계산'이다. 그러나 여기에 '휴대성'이라는 특성이 추가되었다.

따라서 전화기와 컴퓨터는 기초 클래스, 무선 전화기와 노트북 컴퓨터는 각각의 유도 클래스로 정의한다.

다시 말해서,

무선 전화기 is a 전화기

- 노트북 컴퓨터 is a 컴퓨터


즉, 상속관계가 성립하려면 기초 클래스와 유도 클래스간에 IS-A 관계가 성립해야 한다.


(2) HAS-A 관계도 상속의 조건

'HAS-A 관계'도 상속의 조건은 되지만, 복합 관계로 이를 대신하는 것이 일반적이다.

유도 클래스는 기초 클래스가 지니고 있는 모든 것을 '소유'하므로, 소유의 관계도 상속으로 표현이 가능하다.

예를들어, '경찰관'은 '총'을 소지하고 있다. 즉, "경찰관 has a 총" 이므로

gun을 기초 클래스로, 경찰관을 유도 클래스로 정의할 수 있다.

※ 그러나, 기능이 추가될 경우 요구 사항을 반영하기가 쉽지 않다.

- 권총을 소유하지 않은 경찰을 표현해야 한다.

- 경찰이 권총과 수갑뿐만 아니라, 전기봉도 소유하기 시작했습니다.

'총'을 상속받은 '경찰관'의 경우, 위의 요구 사항을 반영하기가 어렵다.


즉, 단순히 클래스 내부에 객체로 선언하는 것이 바람직하다.


출처 : 윤성우 열혈 C++ 프로그래밍