상속(inheritance)

이재원·2024년 5월 30일
0

C++

목록 보기
3/11
post-custom-banner

클래스 상속

C++에서 부모 클래스를 기본 클래스(base class), 자식 클래스를 파생 클래스(derived class)라고 부른며, 다중 상속을 허용한다. 상속 시에는 연관성이 있는 클래스만 상속하는 것이 옳바르다

상속은 아래와 코드와 같이 클래스 뒤에 콜론(:)과 기본 클래스 이름을 선언하면 된다.

class Point {
	int x, y;
public:
	void set(int x, int y);
	void showPoint() const;
};

class ColorPoint: public Point {
	...
}

상속의 목적과 장점

  • 중복의 제거
  • 계층 관계로 표현함으로써 분류 및 관리 용이
  • 클래스의 재사용과 확장을 통한 소프트웨어의 생산성 향상

파생 클래스 객체와 멤버 호출

기본 클래스를 상속받은 파생 클래스는 기본 클래스의 public 멤버에 언제든지 접근이 가능하다. 또한 기본 클래스의 private 멤버 변수도 파생 클래스에 상속이 된다.(상속과 동시에 생성됨)하지만 파생 클래스에서 기본 클래스의 private 멤버 변수에 직접 접근은 불가하기 때문에 getter와 setter로 접근해야 한다.

상속과 객체 포인터

파생 클래스의 객체에는 기본 클래스에서 선언된 멤버들과 파생 클래스에서 선언한 멤버들이 모두 존재하기 때문에, 파생 클래스의 객체를 파생 클래스의 포인터나 기본 클래스의 포인터로 모두가리킬 수 있다.

업 캐스팅과 다운 캐스팅

들어가기에 앞서 간략하게 정리를 해보면, 업 캐스팅을 할 경우에는 기본 클래스의 public 멤버에만 접근이 가능하다. 다운 캐스팅을 할 경우에는 기본 클래스 뿐만 아니라 파생 클래스의 public 멤버 모두에게 접근이 가능하다. 그럼 이제 자세히 설명해 보겠다.

업 캐스팅

파생 클래스의 객체를 기본 클래스의 포인터로 가리키는 것을 말한다.

기본 클래스 Point 타입의 포인터 pBase로 파생 클래스인 ColorPoint 객체를 가리킨다. 하지만 pBase는 Point 클래스의 포인터이므로, pBase 포인터로는 ColorPoint 객체 내의 Point 클래스 멤버만 접근할 수 있다. 따라서 아래 문장은 컴파일 오류가 발생한다.

pBase->showColorPoint(); // showColorPoint()는 Point의 멤버가 아님.

즉, 업 캐스팅한 기본 클래스의 포인터로는 기본 클래스의 멤버만 접근 가능하다.

또한 업 캐스팅시 명시적 타입 변환이 필요없다. 왜냐하면 cp 객체는 ColorPoint 타입이지만 동시에 Point 타입이기도 하기 때문이다.

다운 캐스팅

기본 클래스 포인터가 가리키는 객체를 파생 클래스의 포인터로 가리키는 것을 말한다. 업캐스팅과 달리 명시적 타입 변환 필요하다.

pBase로는 cp 객체의 멤버 중 Point의 public 멤버만 접근할 수 있지만, pDer로는 cp 객체의 모든 public 멤버를 접근할 수 있다.

다음은 다운 캐스팅시 주의해야 할 상황이다.

다운 캐스팅 후, pDer은 ColorPoint 타입의 포인터이므로 다음 라인에서 pDer로 setColor() 함수를 호출하는 데에 문법적인 오류는 없지만, pDer이 가리키는 객체 공간에는 setColor() 함수가 없기 때문에 런타임 에러가 발생한다.

protected 접근 지정

기본 클래스에 protected로 지정된 멤버는 파생 클래스에게 접근을 허용하고, 다른 클래스나 외부 함수에서는 접근할 수 없도록 숨겨진다.

생성자와 소멸자

생성자의 실행 순서

파생 클래스의 객체가 생성될 때 파생 클래스의 생성자와 기본 클래스의 생성자는 모두 실행된다.

생성자는 객체를 초기화할 목적으로 사용되므로, 파생 클래스의 생성자는 파생 클래스의 멤버를 초기화하거나 필요한 초기 작업을 수행하고, 기본 클래스의 생성자는 기본 클래스의 멤버 초기화나 필요한 초기화를 각각 수행한다.

기본 클래스의 생성자가 먼저 실행된 후 파생 클래스의 생성자가 실행된다.
→ 모든 호출 관계는 컴파일러에 의해 이루어짐.

소멸자의 실행 순서

  • 생성자의 실행 순서와 반대로 실행된다
  • 파생 클래스 작성시 함께 실행할 기본 클래스의 생성자도 함께 지정해 줘야 한다.

파생 클래스에서 기본 클래스 생성자 호출

원래는 파생 클래스의 생성자와 함께 실행할 기본 클래스의 생성자를 지정해야 하지만, 파생 클래스의 각 생성자에 대해 함께 실행될 기본 클래스의 생성자를 명식적으로 지정하지 않으면 컴파일러는 묵시적으로 기본 클래스의 기본 생성자가 실행되도록 컴파일한다.

묵시적 기본 클래스의 생성자 선택

명시적으로 지정하지 않을 경우, 컴파일러는 묵시적으로 기본 클래스의 기본 생성자가 실행되도록 컴파일 한다.

기본 생성자가 선언되어 있지 않다면 컴파일러는 오류를 발생시킨다.

파생 클래스의 매개 변수를 가진 생성자에 대해서도 컴파일러는 묵시적으로 기본 클래스의 기본 생성자를 호출하도록 컴파일한다.

명시적인 기본 클래스의 생성자 선택

컴파일 단계에서 묵시적으로 호출되는 것이 싫다면, 명시적으로 기본 클래스의 생성자를 선언해주면 된다

상속의 종류

접근 지정자

public 상속

기본 클래스를 public으로 상속받으면, 기본 클래스의 protected, public 멤버들은 접근 지정 변경 없이 파생 클래스에 그대로 상속된다.

protected 상속

기본 클래스를 protected로 상속받으면, 기본 클래스의 protected, public 멤버들은 모두 protected 접근 지정으로 변경되어 상속된다.

private 상속

기본 클래스를 private로 상속받거나 접근 지정자를 생략하고 상속한다면, 기본 클래스의 protected, public 멤버들은 모두 private 접근 지정으로 변경되어 상속된다.

중첩된 상속

상속이 중첩된다 하더라도 가장 하위 파생 클래스에서 모든 클래스의 멤버에 접근이 가능한 것은 아니다.

#include <iostream>
using namespace std;

class Base {
  int a;
protected:
  void setA(int a) {
    this->a = a;
  }
public:
  void showA() {
    cout << a;
  }
};

class Derived: private Base {
  int b;
protected:
  void setB(int b) {
    this->b = b;
  }
public:
  void showB() {
    setA(5); // ok
    showA(); // ok
    cout << b;
  }
};

class GrandDerived: private Derived {
  int c;
protected:
  void setAB(int x) {
    setA(x); // err
    showA(); // err
    setB(x); // ok
  }
}

GrandDerived 클래스에서 setA()와 showA()는 private 속성으로 변경되어 Derived 클래스에 상속되었기 때문에 접근 불가능하다.

setB()의 경우에는 Derived 클래스의 protected 멤버이기 때문에 접근 가능하다.

출처
명품 C++ Programming - 황기태

profile
20학번 새내기^^(였음..)
post-custom-banner

0개의 댓글