상속이란?
- 상속은 부모에게 무엇인가를 물려받는 것을 의미한다.
- 부모가 되는 클래스를 기본 클래스라 하고 자식 클래스에 해당되는 클래스를 파생 클래스라고 한다.
- 자식 클래스는 부모 클래스의 데이터 필드와 함수 모두를 상속받으며, 자신만의 데이터 필드나 함수를 추가할 수 있다.
- 상속을 이용하여 공통적 기능은 기본 클래스로 정의하고, 나중에 좀 더 특별한 클래스로 확장이 가능하다.
보호(protected) 키워드
파생 클래스에서 기본 클래스의 데이터 필드나 함수에 접근하도록 허용하려면 protected 키워드를 사용한다. protected 멤버는 정의된 클래스 내부와 파생 클래스에서만 접근이 가능하다. (+public 멤버는 다른 클래스나 클라이언트 프로그램에서 접근 가능하며, private 멤버는 정의된 클래스 내부 또는 friend 함수와 클래스에서만 접근할 수 있으며, 자식 클래스에서도 접근 불가능)
class B {
public: int i;
protected: int j;
private: int k;
};
class A : public B {
public:
void display() const {
cout << i << endl; // 접근가능
cout << j << endl; // 접근가능
cout << k << endl; // 접근불가
}
};
int main() {
A a;
cout << a.i << endl; // 접근가능
cout << a.j << endl; // 접근불가
cout << a.k << endl; // 접근불가
return 0;
}
생성자와 소멸자
- 데이터 필드나 함수와 달리 기본 클래스의 생성자와 소멸자는 파생 클래스로 상속되지 않는다.
- 기본 클래스의 생성자는 데이터 필드를 초기화하기 위해서 파생 클래스의 생성자로부터(명시적이든 암시적이든) 호출만 가능하다.
- 만약 기본 클래스의 생성자가 호출되지 않으면, 아래처럼 기본 클래스의 인수 없는 생성자가 기본적으로 호출된다.
- 파생 클래스의 소멸자가 호출될 때 자동으로 기본 클래스의 소멸자가 호출된다.
- 파생 클래스의 생성자로부터 명시적으로 기본 클래스의 생성자를 호출하려면 초기화 리스트를 이용한다.
- 상속된 멤버변수의 메모리할당은 기본 클래스의 생성자에 의해 이루어지도록 하는 것이 원칙이다.
생성자와 소멸자의 연쇄적 처리
클래스의 인스턴스를 생성할 때 상속 연결을 따라 모든 기본 클래스들의 생성자를 호출한다. 그러므로 기본 클래스의 생성자는 파생 클래스의 생성자보다 먼저 호출되는 반면 소멸자는 파생 클래스의 소멸자가 먼저 호출되는 역순으로 자동 호출된다. 이를 생성자 및 소멸자의 연쇄적 처리라고 함
class Person {
public:
Person() { cout << "Person 생성자" << endl; }
~Person() {
cout << "Person 소멸자" << endl; }
};
class Employee : public Person {
public:
Employee() { cout << "Employee 생성자" << endl; }
~Employee() { cout << "Employee 소멸자" << endl; }
};
class Faculty : public Employee {
public:
Faculty() { cout << "Faculty 생성자" << endl; }
~Faculty() { cout << "Faculty 소멸자" << endl; }
};
//생성자와 소멸자의 연쇄적 처리 테스트
int main() {
Faculty* fp = new Faculty();
delete fp;
fp = NULL;
return 0;
}
//<출력 결과>
/* Person 생성자
Employee 생성자
Faculty 생성자
Faculty 소멸자
Employee 소멸자
Person 소멸자 */
}
함수 오버라이딩
- 함수의 오버라이딩이란 기본 클래스에서 이미 정의되었지만 이를 상속하는 파생 클래스에서 객체에 맞는 좀 더 구체적인 설명을 반환하기 위해 함수를 재정의하는 것을 말한다. 이때, 기본클래스의 함수와 동일한 서명, 반환 타입을 가져야 한다.
함수 오버라이딩 대 오버로딩
- 함수 오버로딩은 같은 이름을 갖는 하나 이상의 함수를 제공하는 방법으로 구별하기 위해서는 서로 다른 함수 서명(function signature)을 가져야 한다. 함수 오버라이딩은 기본 클래스에서의 함수와 동일한 함수 서명과 동일한 반환 유형을 사용하여 파생 클래스에서 함수를 재정의하는 것을 의미한다.
업캐스팅과 다운캐스팅
- 파생 클래스 유형의 포인터를 기본 클래스 유형의 포인터로 할당하는 것을 업캐스팅(upcasting)이라 하고, 기본 클래스 유형의 포인터를 파생 클래스 유형의 포인터로 할당하는 것을 다운캐스팅(downcasting)이라 한다.
- 업캐스팅은 암시적으로 수행될 수 있지만, 다운캐스팅은 특별한 경우가 아니고는 작동하지 않으며, 명시적으로 캐스팅해야한다.
가상함수란?
- 가상함수는 동적결합을 사용해 호출한다. 동적결합(dynamic binding)은 컴파일 시에 호출될 함수가 결정되는 정적 결합(static binding)과 달리 프로그램 실행 시에 호출될 함수가 결정되므로, 실제 저장된 객체의 타입에 맞춰 함수를 호출한다.
- 함수에 대해 동적 결합이 가능하도록 하기 위해서는 1. 함수는 기본 클래스에서 virtual로 정의되어야 한다.(virtual 키워드를 파생 클래스의 함수선언에 추가할 필요는X) 2. 객체를 참조하는 변수는 참조에 의해 전달되거나 포인터로 전달되어야 한다.
- 만약 기본 클래스에서 정의한 함수를 파생 클래스에서 재정의해야한다면, 혼동과 실수가 발생하지 않도록 가상으로 정의해야 한다.
- 이와는 반대로, 함수가 재정의되지 않을 경우에는 실행 시에 동적으로 가상 함수를 결합하는데 필요한 시간과 시스템 자원이 더 소요되지 않도록 가상으로 선언하지 않는 것이 더 효과적이다.
소멸자
- 상속 관계에 있는 부모 클래스의 소멸자는 반드시 가상으로 선언
- 소멸자가 가상이면 동적 결합하므로 Child의 소멸자가 호출되고 그런 다음 Parent의 소멸자가 호출된다.
class Person {
public:
Person() { cout << "Person 생성자" << endl; }
~Person() { cout << "Person 소멸자" << endl; }
//virtual ~Person() { cout << "Person 소멸자" << endl; }
//virtual로 소멸자를 선언할 경우 자식소멸자가 호출된 뒤에 부모소멸자가 호출된다.
};
class Employee : public Person {
public:
Employee() { cout << "Employee 생성자" << endl; }
~Employee() { cout << "Employee 소멸자" << endl; }
};
class Faculty : public Employee {
public:
Faculty() { cout << "Faculty 생성자" << endl; }
~Faculty() { cout << "Faculty 소멸자" << endl; }
};
//생성자와 소멸자의 연쇄적 처리 테스트
int main() {
Person* fp = new Faculty();
delete fp;
fp = NULL;
return 0;
}
//<출력 결과>
/* Person 생성자
Employee 생성자
Faculty 생성자
Person 소멸자 */
완전순수가상함수(추상함수)
- 순수가상함수는 이 함수는 정의가 없지만 자식 클랫에서 함수를 오버라이딩하니 다형성을 사용하여 호출되는 함수이다.
- virtual double getArea() const = 0; 와 같이 함수의 정의없이 유형만을 제시해 놓는 것
추상 클래스
완전가상함수를 하나 이상 가지고 있는 클래스는 추상 클래스가 되며 이 클래스는 객체를 생성할 수 없다. 추상 클래스의 용도는 파생클래스가 완전가상함수를 반드시 구현해야한다는 강제성을 부여하여, 여러 종류의 관련 클래스를 동일한 함수로 접근할 수 있도록 하는 것이다.
다형성(Polymorphism)
- 다형성이란 다양한(poly) 형태(morph)를 갖는다는 뜻
- 객체지향에서 다형성이란 타입에 관계없이 동일한 방법으로 다룰 수 있는 능력이다. 상위 유형의 변수가 하위유형을 가리킬 수 있음 의미하기도 하며, 가상함수를 써서 한 함수의 이름에 여러 의미를 연관시키는 것을 의미하기도.
- 다형성, 업캐스팅, 동적결합(사후바인딩), 가상함수는 실제로 모두 같은 주제이며, C++에서 가상함수를 포함하는 유형을 다형성 유형이라고 부른다.
'프로그래밍 공부흔적 > C++' 카테고리의 다른 글
[C++] 연산자 오버로딩 (0) | 2021.06.06 |
---|---|
[C++] 객체와 클래스에 대한 이야기 정리 (0) | 2021.04.18 |
[C++] 해시 문제를 풀 때 쓴 vector STL 정리 (0) | 2020.12.30 |
[C++] 배열에 대한 이야기들 정리 (0) | 2020.12.02 |
댓글