devRenio

상속

객체지향프로그래밍II(김정준) 중간고사 대비 (8장)

C++ 상속

  • 클래스 사이에서 상속 관계 정의
    ◼ 객체 사이에는 상속 관계 없음
  • 기본 클래스의 속성과 기능을 파생 클래스에 물려주는 것
    기본 클래스(base class) - 상속해주는 클래스. 부모 클래스
    파생 클래스(derived class) – 상속받는 클래스. 자식 클래스
    ◼ 기본 클래스의 속성과 기능을 물려받고 자신만의 속성과 기능을 추가/수정하여 작성
  • 기본 클래스에서 파생 클래스로 갈수록 클래스의 개념이 구체화
  • 다중 상속을 통한 클래스의 재활용성 높임

상속의 목적/장점

  1. 간결한 클래스 작성
    기본 클래스의 기능을 물려받아 파생 클래스를 간결하게 작성
  2. 클래스 간의 계층적 분류 및 관리의 용이함
    상속은 클래스들의 구조적 관계 파악 용이
  3. 클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
    빠른 소프트웨어 생산 필요
    기존에 작성한 클래스의 재사용 – 상속받아 새로운 기능을 확장
    앞으로 있을 상속에 대비한 클래스의 객체 지향적 설계 필요

상속 선언

class Person { // 기본 클래스 Person 선언
  int age; // private 변수이므로 Person에서만 접근 가능
public:
  void setAge(int age) { this->age = age; }
  void show() { cout << age; }
};

class Student : public Person { // Person을 상속받는 Student 선언
  double score;
public:
  void setScore(double score) { this->score = score; }
  void showStudent() {
    cout << score << ' : ';
    show(); // 파생 클래스에서 기본 클래스 멤버 호출
  }
};

// Student를 상속받는 Intern 선언
class Intern : public Student { ... };


상속과 객체 포인터 – 업 캐스팅

  • 업 캐스팅(up-casting)
    • 파생 클래스 포인터가 기본 클래스 포인터에 치환되는 것
      ◼ 예) 학생을 사람으로 봄
int main() {
  Student mark;
  Student *pDer = &mark;
  Person *pBase = pDer; // 업 캐스팅

  pDer->setAge(20); // mark의 age를 20으로 설정
  pBase->show(); // 20 출력
  pDer->setScore(4.42); // mark의 score를 4.42로 설정
  pDer->showStudent(); // 4.42 : 20 출력
  pBase->showStudent(); // 컴파일 오류. Person에서 Student의 멤버 접근 불가
}

출력 :
20
4.42 : 20


상속과 객체 포인터 – 다운 캐스팅

  • 다운 캐스팅(down-casting)
    • 기본 클래스 포인터가 파생 클래스 포인터에 치환되는 것
      ◼ 예) 사람을 학생으로 봄
int main() {
  Student mark;
  Student *pDer;
  Person *pBase = &mark; // 업 캐스팅

  pBase->setAge(20); // mark의 age를 20으로 설정
  pBase->show(); // 20 출력

  pDer = (Student *)pBase; // 다운 캐스팅
  pDer->setScore(4.42); // mark의 score를 4.42로 설정
  pDer->showStudent(); // 4.42 : 20 출력
}


protected 접근 지정

  • 접근 지정자
    • private 멤버
      ◼ 선언된 클래스 내에서만 접근 가능
      파생 클래스에서도 기본 클래스의 private 멤버 직접 접근 불가
    • public 멤버
      ◼ 선언된 클래스나 외부 어떤 클래스, 모든 외부 함수에 접근 허용
      파생 클래스에서 기본 클래스의 public 멤버 접근 가능
    • protected 멤버
      ◼ 선언된 클래스에서 접근 가능
      파생 클래스에서만 접근 허용
      ◼ 파생 클래스가 아닌 다른 클래스나 외부 함수에서는 protected 멤버 접근 불가
class Person { // 기본 클래스 Person 선언
protected:
  int age; // protected 변수이므로 파생 클래스 멤버함수까지만 접근 가능
public:
  void setAge(int age) { this->age = age; }
  void show() { cout << age; }
};

class Student : public Person { // Person을 상속받는 Student 선언
  double score;
public:
  void setScore(double score) { this->score = score; }
  void showStudent() {
    cout << score << ' : ';
    cout << age; // protected 변수를 파생 클래스에서 접근
  }
};


상속 관계의 생성자와 소멸자 실행

  • 파생 클래스의 객체가 생성될 때
  • 파생 클래스와 기본 클래스의 생성자가 모두 실행
  • 기본 클래스의 생성자가 먼저 실행 (기본 -> 파생)
    • 파생 클래스 생성자 내에서 컴파일러가 기본 클래스 생성자를 호출
    • 생성자의 실행 자체는 파생 클래스가 먼저지만, 본문 진행은 기본 클래스가 먼저
    • 기본 클래스에 기본 생성자(매개 변수가 없는 생성자)가 없는 경우 컴파일 오류 발생
    • 파생 클래스의 생성자에 매개 변수가 있더라도 기본 클래스의 기본 생성자를 선택
    • Derived(int x) : Base(x + 3) { … } 형식으로 명시적으로 생성자 선택 가능
  • 소멸자는 생성자의 반대 순서 실행 (파생 -> 기본)


상속 지정

class Derived : public Base { ... };

class Derived : protected Base { ... };

class Derived : private Base { ... };
  • 상속 선언 시 public, private, protected의 3가지 중 하나 지정
  • 기본 클래스의 멤버의 접근 속성을 어떻게 계승할지 지정
    • public : 기본 클래스의 protected, public 멤버 속성을 그대로 계승
      ◼ 모두 public으로 계승하지 않음
      ◼ public은 public으로, protected는 protected로 계승
    • private – 기본 클래스의 protected, public 멤버를 private으로 계승
    • protected – 기본 클래스의 protected, public 멤버를 protected로 계승


다중 상속

  • 상속받고자 하는 기본 클래스를 ‘,’로 나열하여 다중 상속이 가능
  • 파생 클래스는 나열한 모든 기본 클래스의 멤버를 상속
class Person {
public:
    int age;
    void eat() { ... }
    void sleep() { ... }
};

class Student : public Person {
protected:
    string studentID;
public:
    void study() { ... }
};

class Employee : public Person {
protected:
    string employeeID;
public:
    void work() { ... }
};

// Intern은 Student와 Employee를 모두 상속
class Intern : public Student, public Employee { // 다중 상속 선언
public:
    Intern(int age, string& sID, string& eID) {
        this->age = age; // Person으로부터 상속, 모호성 발생
        this->studentID = sID; // Student로부터 상속
        this->employeeID = eID; // Employee로부터 상속
    }
};

int main() {
  Intern Bob(24, "2024E7403", "20253047"); // 모호성으로 24 전달 불가
  while(true) {
    Bob.eat(); // Person으로부터 상속, 모호성 발생
    Bob.study(); // Student로부터 상속
    Bob.work(); // Employee로부터 상속
    Bob.sleep(); // Person으로부터 상속, 모호성 발생
  }
}


다중 상속의 문제점

  • 기본 클래스 멤버의 중복 상속 (다이아몬드 문제)
    • Person의 멤버가 이중으로 객체에 상속되는 문제 발생
    • 두 번씩 상속받은 age, eat(), sleep()에 접근 시 모호성 발생


가상 상속

  • 다중 상속으로 인한 기본 클래스 멤버의 중복 상속 해결
  • 가상 상속
    • 파생 클래스의 선언문에서 기본 클래스 앞에 virtual로 선언
    • 파생 클래스의 객체가 생성될 때 기본 클래스의 멤버는 오직 한 번만 생성
      ◼ 기본 클래스의 멤버가 중복하여 생성되는 것을 방지
    • Student와 Employee는 Person의 생성자 상속 책임을 Intern에게 넘김
      ◼ 생성자 호출의 관점에서만 보면 Person은 Intern의 조부모가 아니라 부모 판정
class Person { ... };

// 가상 상속
class Student : virtual public Person { ... };

// 가상 상속
class Employee : virtual public Person { ... };

// 가상 상속을 통해 공통의 Person 기본 클래스를 한 번만 포함
class Intern : public Student, public Employee { ... };