[Effecitve C++] 34. 인터페이스 상속과 구현 상속의 차이를 제대로 파악하고 구별하자
public 상속의 두 가지 개념
1. 함수 인터페이스 상속
2. 함수 구현 상속
이 둘의 차이는 함수 선언과 함수 정의의 차이와 맥을 같이 한다.
아래의 예시를 보자.
class Shape
{
public:
virtual void draw() const = 0; // 순수 가상 함수
virtual void error(const std::string& msg); // 단순 가상 함수
int objectID() const; // 비가상 함수
};
class Rectangle: public Shape{};
class Ellipse: public Shape{};
Shape a; // 에러. 인스턴스를 만들 수 없음
Shape는 추상 클래스이다. 왜냐하면 멤버 함수인 draw()가 순수 가상 함수이기 때문이다. 추상 클래스이기 때문에 Shape는 인스턴스를 만들 수 없고, 이 클래스의 파생 클래스에서 인스턴스화 해야 한다.
이 기본 클래스는 파생 클래스에 막대한 영향을 주는데, 멤버 함수 인터페이스는 항상 상속되게 되어있기 때문이다. 즉, 기본 클래스에 있는 모든 것들이 파생 클래스에도 동작해야 한다.
Shape에는 세 개의 함수가 선언되어 있고, 각각 선언된 형태가 다르다. 각각의 의미를 알아보자.
각 함수에 대한 설명
순수 가상 함수
순수 가상 함수의 가장 큰 특징은 2가지이다.
1. 순수 가상 함수를 물려받은 클래스는 해당 순수 가상 함수를 다시 선언해야 한다.
2. 순수 가상 함수는 추상 클래스 안에서 정의를 갖지 않는다.
이걸 종합하면 순수 가상 함수를 선언하는 목적은 파생 클래스에게 함수의 인터페이스(선언)만을 물려주는 것이다.
그리고 순수 가상 함수에도 정의(구현)를 제공할 수 있는데, 구현이 붙은 순수 가상 함수를 호출하려면 반드시 클래스 이름을 한정자로 붙여주어야 한다.
virtual void draw() const = 0
{
std::cout<<"Shape Draw"<<std::endl;
}
Shape::draw();
단순(비순수) 가상 함수
단순 가상 함수를 선언하는 목적은 파생 클래스로 하여금 함수의 인터페이스와 그 함수의 기본 구현도 물려받게 하는 것이다.
단순 가상 함수는 기본 클래스의 버전을 사용할 수도 있고, 다르게 처리하고자 오버라이드를 통해 기본 클래스와 다르게 구현할 수도 있다.
비가상 함수
비가상 함수를 선언하는 목적은 파생 클래스가 함수 인터페이스와 더불어 그 함수의 필수적인 구현을 물려받게 하는 것이다.
비가상 함수는 클래스 파생에 상관없는 불변 동작과 같기 때문에, 파생 클래스에서 재정의할 수 있는 것이 아니다.
클래스 설계 시 주의할 점
순수 가상 함수, 단순 가상 함수, 비가상 함수의 선언문이 가진 차이점 덕분에 파생 클래스가 물려받았으면 하는 것들을 정밀하게 지정할 수 있다.
인터페이스만 상속시켜도 되고(순수 가상 함수), 인터페이스와 기본 구현을 함께 상속시킬 수도 있고(가상 함수), 인터페이스와 필수 구현을 상속시킬 수도 있다.(비가상 함수)
멤버 함수 작성 시 하는 실수
모든 멤버를 비가상 함수로 선언하는 것
- 파생 클래스를 만들더라도 기본 클래스의 동작을 특별하게 만들만한 여지가 없어지게 된다.
- 비가상 소멸자가 문제가 될 수 있다.
모든 멤버를 가상 함수로 선언하는 것
- 파생 클래스에서 재정의가 안되어야 하는 함수도 있을 것이다.
- 이런 함수는 반드시 비가상 함수로 만들어서 의도를 보여야 한다.
- 모든 멤버를 가상 함수로 선언하는 것이 맞는 경우인 인터페이스 클래스도 존재한다.
요약
- 인터페이스 상속은 구현 상속과 다르다. public 상속에서, 파생 클래스는 항상 기본 클래스의 인터페이스를 모두 물려받는다.
- 순수 가상 함수는 인터페이스 상속만을 허용한다.
- 단순(비순수) 가상 함수는 인터페이스 상속과 더불어 기본 구현의 상속도 가능하도록 지정한다.
- 비가상 함수는 인터페이스 상속과 더불어 필수 구현의 상속도 가하도록 지정한다.
'Books > Effective C++' 카테고리의 다른 글
[Effective C++] 36. 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물! (0) | 2022.07.02 |
---|---|
[Effective C++] 35. 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자 (0) | 2022.06.26 |
[Effective C++] 33. 상속된 이름을 숨기는 일은 피하자 (0) | 2022.06.25 |
[Effective C++] 32. public 상속 모형은 반드시 "is-a(..는..의 일종이다)"를 따르도록 만들자 (0) | 2022.06.25 |
[Effective C++] 31. 파일 사이의 컴파일 의존성을 최대로 줄이자 (0) | 2022.06.25 |
댓글
이 글 공유하기
다른 글
-
[Effective C++] 36. 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!
[Effective C++] 36. 상속받은 비가상 함수를 파생 클래스에서 재정의하는 것은 절대 금물!
2022.07.02 -
[Effective C++] 35. 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자
[Effective C++] 35. 가상 함수 대신 쓸 것들도 생각해 두는 자세를 시시때때로 길러 두자
2022.06.26 -
[Effective C++] 33. 상속된 이름을 숨기는 일은 피하자
[Effective C++] 33. 상속된 이름을 숨기는 일은 피하자
2022.06.25 -
[Effective C++] 32. public 상속 모형은 반드시 "is-a(..는..의 일종이다)"를 따르도록 만들자
[Effective C++] 32. public 상속 모형은 반드시 "is-a(..는..의 일종이다)"를 따르도록 만들자
2022.06.25