객체지향프로그래밍II(김정준) 중간고사 대비 (8장)
C++ 상속
- 클래스 사이에서 상속 관계 정의
◼ 객체 사이에는 상속 관계 없음 - 기본 클래스의 속성과 기능을 파생 클래스에 물려주는 것
◼ 기본 클래스(base class) - 상속해주는 클래스. 부모 클래스
◼ 파생 클래스(derived class) – 상속받는 클래스. 자식 클래스
◼ 기본 클래스의 속성과 기능을 물려받고 자신만의 속성과 기능을 추가/수정하여 작성 - 기본 클래스에서 파생 클래스로 갈수록 클래스의 개념이 구체화
- 다중 상속을 통한 클래스의 재활용성 높임
상속의 목적/장점
- 간결한 클래스 작성
기본 클래스의 기능을 물려받아 파생 클래스를 간결하게 작성 - 클래스 간의 계층적 분류 및 관리의 용이함
상속은 클래스들의 구조적 관계 파악 용이 - 클래스 재사용과 확장을 통한 소프트웨어 생산성 향상
빠른 소프트웨어 생산 필요
기존에 작성한 클래스의 재사용 – 상속받아 새로운 기능을 확장
앞으로 있을 상속에 대비한 클래스의 객체 지향적 설계 필요
상속 선언
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 멤버 접근 불가
- private 멤버
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로 계승
- public : 기본 클래스의 protected, public 멤버 속성을 그대로 계승
다중 상속
- 상속받고자 하는 기본 클래스를 ‘,’로 나열하여 다중 상속이 가능
- 파생 클래스는 나열한 모든 기본 클래스의 멤버를 상속
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 { ... };