C++ 클래스 상속(class inheritance)

프로그래밍/C++ 2019.10.18 댓글 Plorence

C++는 클래스를 확장하고 수정하기 위해 단순한 코드 수정보다 더 강력한 수단을 제공합니다.

바로 클래스 상속입니다.

기초 클래스(base class)라 부르는 클래스로부터 모든 멤버 함수와 멤버들을 상속받고 새로운 파생 클래스(clerived class)를 만들 수 있게 합니다.

 

상속으로 할 수 있는 일

  • 기존의 클래스에 기능을 추가할 수 있습니다.

  • 클래스가 나타내고 있는 데이터에 다른 것을 더 추가할 수 있습니다.

  • 클래스 멤버 함수가 동작하는 방식을 변경할 수 있습니다.

상속의 의미

어떤클래스를 다른 클래스로부터 상속할 때, 오리지널 클래스를 기초 클래스(base class)라 하고 상속받는 클래스를 파생 클래스(derived class)라고 합니다.

그림으로 설명

위에서도 언급했지만 상속받게되면 멤버, 멤버 함수에 접근 및 추가가 가능하게 됩니다.

Class2는 Class1을 포함한다라고 이해하면 됩니다.

 

간단한 상속해보기

#include <iostream>
class Class1 {
private:
        int A = 0;
public:
        Class1() {
        }
        void Show() {
               std::cout << "Hello World!";
        }
};
class Class2 : public Class1{ //Class1을 상속받는다.
private:
        int B = 0;
public:
        Class2(){
        }
};
int main(void) {
        Class2 test = Class2();
        test.Show();
        return 0;
}

상속은 클래스 네임 뒤에 콜론을 써주고 클래스명을 써주면 상속받게 됩니다.

상속받을 클래스 앞에 public이 붙었는데 이것을 public 파생(public derivation)이라고 합니다.

public 파생에서는 기초 클래스의 public 멤버들이 파생 클래스의 public 멤버가 됩니다.

기초 클래스의 private부분도 파생 클래스의 일부가 됩니다.

하지만 private의 특성에 따라 protected 멤버 함수나 public 멤버 함수를 통해서 접근할 수 있습니다.

이것은 나중에 배우게 됩니다.

 

생성자에 대하여

파생 클래스의 객체를 생성할 때, 먼저 기초 클래스의 객체를 생성합니다.

즉 파생 클래스의 생성자의 몸체 안으로 들어가기 전에, 기초 클래스 객체 먼저 생성되어야 한다는 것을 의미합니다.

기초 클래스의 생성자가 명시적으로 호출되어야 한다면 파생 클래스의 생성자에서 멤버 초기자 리스트를 사용하면 됩니다.

#include <iostream>
class Class1 {
private:
        int A = 0;
public:
        Class1(int p_a) {
               A = p_a;
        }
        void Show() {
               std::cout << "Hello World!";
        }
};
class Class2 : public Class1{ //Class1을 상속받는다.
private:
        int B = 0;
public:
        Class2() : Class1(10){ //멤버 초기자 리스트를 사용
        }
};
int main(void) {
        Class2 test = Class2();
        test.Show();
        return 0;
}

이처럼 파생 클래스의 생성자에 멤버 초기자 리스트를 사용하여야 합니다.

만약 멤버 초기자 리스트를 안 써주게 된다면 디폴트 기초 클래스 생성자나 파라미터가 필요 없는 생성자가 호출됩니다.

 

기초 클래스와 파생 클래스의 관계

기초 클래스 포인터는 명시적 데이터형 변환 없이도 파생 클래스 객체를 지시할 수 있고 참조도 마찬가지입니다.

#include <iostream>
class Class1 {
private:
        int A = 0;
public:
        Class1(int p_a) {
               A = p_a;
        }
        void Show() {
               std::cout << "Hello World!";
        }
};
class Class2 : public Class1{ //Class1을 상속받는다.
private:
        int B = 0;
public:
        Class2() : Class1(10){ //멤버 초기자 리스트를 사용
        }
        void Show() {
               std::cout << "Hello World!" << B << std::endl;
        }
};
int main(void) {
        Class2 * ptr = new Class2();
        Class1 * ptr2 = ptr;
        ptr2->Show();
        return 0;
}

이처럼 기초 클래스 포인터는 파생 클래스 객체를 지시할 수 있습니다.

(다만 기초 클래스 멤버 함수 한정입니다. 당연한 겁니다.)

참조도 마찬가지고요.

일반적으로 참조형이나 포인터형이나 대입되는 데이터형이 일치할 것을 요구하지만 상속에서는 이 규칙이 완화됩니다.

반대로 파생 클래스 포인터는 기초 클래스 객체를 지시할 수 없습니다.

(명시적 형 변환을 하면 컴파일 에러는 안 나긴 합니다.)

이것을 업캐스팅이라고 부르는데, 나중에 자세하게 배웁니다.

이게 어떻게 가능한 거냐면, 파생 클래스는 기초 클래스의 데이터를 모두 가지고 있기 때문입니다.

함수 파라미터에서도 동일한 규칙이 적용됩니다.

 

 

댓글