과거에 상속을 바라보았던 관점
기존에 정의해 놓은 클래스의 재활용을 목적으로 만들어진 문법적 요소가 상속이다.
→ 그러나 상속은 재활용을 목적으로만 존재하는 문법적 요소가 아니며, 다양한 이점들이 존재한다.
어떤 회사가 직원의 급여를 관리하기 위해 어떤 프로그램을 만들었다. 회사 초기에는 ‘정규직'으로 직원의 종류가 1가지였지만, 점점 회사가 커지면서 직원의 역할이 세분화됨에 따라 직원의 고용형태가 달라졌다고하자. 이미 설계해둔 프로그램을 이용하여 현재의 조건에 맞는 기능을 할 수 있게 변경할 수는 없을까?
→ 상속을 적용하면 가능하다!
자식클래스가 부모클래스를 상속하게 되면, 자식클래스는 부모클래스가 지니고 있는 모든 멤버를 물려받는다.
: 접근제한자 부모클래스이름
을 붙여 상속할 수 있다./*부모클래스 Person*/
class Person {
private:
int age;
char name[50];
public:
Person(int myage, char* myname) : age(myage) {
strcpy(name, myname);
}
void ShowInfo() const {
cout<<"Age: "<<age<<endl;
cout<<"Name: "<<name<<endl;
}
};
/*자식클래스 Student*/
class Student : public Person {
private:
char major[50];
public:
Student(int myage, char* myname, char* mymajor) : Person(myage, myname) {
strcpy(major, mymajor);
}
void ShowInfo_std() const {
ShowInfo();
cout<<"Major: "<<major<<endl;
}
};
major
라는 멤버 변수와 ShowInfo_std()
라는 멤버메소드만 가지고 있는 것 같지만, 실제로는 자기자신의 멤버 + 부모의 멤버(age
, name
, ShowInfo()
)를 모두 멤버로 가지고 있다.Student(int myage, char* myname, char* mymajor) //부모멤버 초기화를 위한 인자요구
: Person(myage, myname) //부모생성자의 호출
{
strcpy(major, mymajor); //자기자신의 멤버의 초기화
}
자식클래스의 객체 생성
클래스를 생성하기 위해 메모리 공간이 할당된다.
이후 객체 생성 명령문에 의해 인자가 전달되면서 생성자가 호출된다.
먼저 부모 클래스의 생성자 호출을 위해 이니셜 라이저를 찾고,
a. 부모클래스의 생성자가 명시되어있으면 명시된 부모클래스의 생성자를 호출한다.
b. 명시되어있지 않다면 void 생성자를 호출한다.
*void 생성자 → 전달받는 인자가 없는 생성자. (default 생성자 or 전달받는 인자없이 초기화하는 생성자)
부모 클래스의 생성자 호출이 완료되어 부모 클래스의 멤버변수가 먼저 초기화된다.
부모 클래스의 생성자 호출을 위해 실행되지 않았던 자식 클래스의 생성자 실행이 마저 진행되면서 자식 클래스의 멤버변수도 초기화된다.
자식 클래스 객체 생성 특징
자식 클래스의 생성자에서 만약 부모 클래스의 생성자 호출을 명시하지 않았다면, 부모 클래스의 default 생성자가 호출된다.
부모 클래스의 생성자 정의에 따른 결과
부모 클래스에서 어떤 생성자도 정의하지 않았을 때
defalt 생성자가 사용된다. 따라서 생성만 했을 뿐 초기화를 위한 어떤 작업도 해주지 않았기 때문에 부모 클래스의 멤버변수에는 쓰레기 값이 들어있다.
부모 클래스에 void생성자가 아닌 다른 생성자(=인자를 전달받는)를 정의했을 때
자식클래스의 객체 소멸
자식 클래스의 소멸자 실행
부모 클래스의 소멸자 실행
⇒ 스택에 생성된 객체의 소멸순서는 생성순서와 반대이다.
Person | Student |
---|---|
상위클래스 | 하위클래스 |
기초(base)클래스 | 유도(derived)클래스 |
슈퍼(super)클래스 | 서브(sub)클래스 |
부모클래스 | 자식클래스 |
private < protected < public
private
와 protected
는 모두 해당 클래스의 외부에서는 접근이 불가능하지만 내부에서는 접근이 가능하다.
그러나 상속에서는 이 2가지 키워드의 차이점을 한눈에 알 수 있다!
private
: 자식클래스는 부모클래스의 외부이기 때문에 private
로 선언된 부모 클래스의 멤버에 접근할 수 없다. → 상속과정에서는 어떤 접근제한범위로 선언된 멤버든 모두 상속된다.(private
멤버도 상속됨) 그러나 접근제한의 기준은 객체가 아니라 클래스이기 때문에, 부모 클래스의 외부인 자식 클래스에서는 private
멤버에 직접접근은 할 수 없다.protected
: 그러나 private
와 다르게 protected
로 선언된 부모 클래스의 멤버는 부모 클래스의 뫼부인 자식 클래스 안에서 접근할 수 있다!자식클래스에게만 제한적으로 접근을 허용한다.
소스코드
/*부모 클래스*/
class Base {
private:
int num1;
protected:
int num2;
public:
int num3;
};
/*자식 클래스*/
class Derived : public Base {
public:
void Show(){
cout<<"private number"<<num1; // 컴파일 에러
cout<<"protected number"<<num2; // 접근 가능!
cout<<"public number"<<num3; // 접근 가능!
}
};
자식 클래스에서 상속을 명시하는데 있어서 3가지의 접근제한자를 모두 사용할 수 있다. [상속의 형태를 명시]
class 자식클래스 :
접근제한자
부모클래스 {};
protected보다 접근의 범위가 넓은 멤버(=public)는 protected로 변경시켜서 상속한다.
→ 따라서 protected 상속을 하게 되면, 기존에 클래스 밖에서 접근이 가능하던 멤버도 밖에서 접근할 수 없도록 보호된다.
private
멤버 → 직접 접근 불가능한 멤버protected
멤버 → protected
멤버public
멤버 → public
멤버private보다 접근의 범위가 넓은 멤버(=public, protected)는 private로 변경시켜서 상속한다.
→ 따라서 private 상속을 한 클래스를 다시 한번 더 상속하게되면 모든 멤버(변수,함수)가 private이기 때문에, 자식클래스에서는 부모클래스의 모든 멤버에 직접접근할 수 없게된다. (사실상 의미없는 상속)
private
멤버 → 직접 접근 불가능한 멤버protected
멤버 → private
멤버public
멤버 → private
멤버public보다 접근의 범위가 넓은 멤버(=없음)는 public으로 변경시켜서 상속한다.
⇒ private를 제외한 나머지 멤버는 모두 그냥 그대로 상속한다.
private
멤버 → 직접 접근 불가능한 멤버protected
멤버 → protected
멤버public
멤버 → public
멤버*실제로는 public 이외의 상속은 다중상속과 같이 특별한 케이스가 아니라면 사용하지 않는다.
상속관계가 성립하기 위해서는 자식 클래스와 부모클래스 간에 IS-A 관계가 성립해야한다.
자식클래스 is a 부모클래스
자식클래스는 부모 클래스가 가지는 모든 멤버를 소유하기 때문에 소유의 관계도 상속으로 표현할 수 있다.
자식클래스 has a 부모클래스
(단, 소유의 관계는 상속이 아니라 가지고 있는 객체를 생성해서 사용하는 등의 방법을 사용하여 표현할 수 있다. )
본문은 ⟪열혈 C++ 프로그래밍, 윤성우⟫ 도서에 기반하여 정리한 내용입니다.