저작권 문제 발생 시, 댓글이나 이메일로 연락 부탁 드립니다.
(문제 발생 시, 비공개 글로 전환할 예정입니다.)
0. 서론
객체지향언어로 코딩을 할 때 상속을 자주 사용하는데 그 상속은 public 상속이다.
c++ 에는 public 상속 말고 private 상속이란 게 존재하는데 이게 무엇인지 왜 존재하는지 궁금했었다.
그 궁금증은 요즘 읽고 있는 “Effective C++” 책에서 해소되었는데 설명이 너무 잘 되어 있어 간단하게 정리를 해보았다.
1. public 상속 - “is-a”
private 상속을 보기 앞서, public 상속이 무엇인지 간단하게 확인해 보자.
public 상속이란 “is-a“관계를 의미한다.
“A is a B” == “A는 B의 일종이다.”
예를 들어, Vehicle 기본 클래스와 Car 파생 클래스가 있다고 가정하자.
1
2
3
4
5
class Vehicle
{};
class Car : public Vehicle
{};
여기서 class Car : public Vehicle는 “Car는 Vehicle의 일종이다(Car is a Vehicle)”라는 의미를 가진다.
그렇기 때문에 아래와 같은 함수가 있을 때
1
2
void FUNC(Vehicle&);
void FUNC2(Car&);
FUNC(Vehicle&)에는 Car 객체가 사용될 수 있지만
FUNC2(Car&) 에는 Vehicle 객체가 사용될 수 없음을 알 수 있다.
1
2
3
4
5
6
7
8
Vehicle ve;
Car car;
FUNC(ve);
FUNC(car); // 성공! Car는 Vehicle입니다.
FUNC2(ve); // 컴파일 에러! Vehicle은 Car가 아닙니다
FUNC2(car);
추가로 “is-a” 관계에서 중요한 점이 하나 더 있는데
기본 클래스가 가지고 있는 성질(Ex. 멤버 함수)을 파생 클래스도 가지고 있어야 한다.
public 상속 관계를 가지는 클래스 간의 약속이라 생각하자.
만약 성질을 가지고 있지 않는다면 어떻게 될까?
예를 들어,
Vehicle은 출발 전 엔진에 시동을 꼭 걸어야 한다고 가정하자.
그냥 그렇다고 치자..
그래서 StartEngine() 가상 멤버 함수를 선언해서 파생 클래스에서 각자의 방법으로 엔진에 시동을 걸 수 있게 하였다.
여기서 Vehicle의 파생 클래스로 Bike 클래스가 새로 추가되었다고 보자.
1
2
3
4
5
6
7
8
9
10
class Vehicle
{
public:
virtual void StartEngine() const;
};
class Bike : public Vehicle
{
};
하지만 Bike(자전거)에는 “엔진”이란 것이 존재하지 않는 건 우리 모두가 알고 있다.
그러므로 Vehicle의 파생 클래스는 StartEngine()을 모두 가지고 있지만, Bike는 StartEngine()이 필요 없다.
즉, “자전거는 (엔진을 키는) Vehicle의 일종이다.”라는의미가 훼손되는 것이다.
위 코드를 실제로 돌려보면 “코드 상”으로는 에러가 발생하지 않는다.
하지만, 사용자 입장에서 봐보면 Bike는 엔진이 없는 걸 알지만
bike.StartEngine()과 같이 bike를 통해서 엔진 함수를 호출할 수 있다.얼마나 당황스러운가..!
그러므로,
위에서 “public 상속 관계를 가지는 클래스 간의 약속이라 생각하자.”이라고 언급한 걸 잊지 말자!
그러므로, public 상속 관계에서는 파생 클래스가 기본 클래스의 모든 내용을 포함하고 있어야 한다.
2. private 상속
private 상속의 큰 특징점은 2가지가 있는데 아래와 같다.
- 클래스 사이 상속 관계가 private일 때, 컴파일러는 파생 클래스 객체를 기본 클래스 객체로 변환을 허용하지 않는다.
- 기본 클래스에서 받은 멤버는 파생 클래스에서 모두 private 멤버가 된다.
두 특징점을 하나씩 알아보자.
2-1. 변환을 허용하지 않는다.
위에서도 설명했지만 public 상속일 때 아래 코드는 문제가 없음을 알 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Vehicle
{};
class Car : public Vehicle
{};
void FUNC(Vehicle&);
int main()
{
Vehicle ve;
Car car;
FUNC(ve);
FUNC(car);
}
여기서 상속을 private 상속으로 바꿔보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Vehicle
{};
class Car : private Vehicle // private 상속
{};
void FUNC(Vehicle&);
int main()
{
Vehicle ve;
Car car;
FUNC(ve);
FUNC(car); // 컴파일 에러 발생!
}
FUNC(car)에서 에러가 발생한 것을 볼 수 있는데 에러 내용은 아래와 같다.
이것을 통해 private 상속은 “is-a” 관계를 뜻하지 않는다는 것을 알 수 있고
그러므로 변환을 허용하지 않는 것을 확인할 수 있다.
2-2. 기본 클래스의 받은 멤버는 파생 클래스에서 모두 private 멤버가 된다.
실제로 모두 private 멤버가 되는지 코드로 확인해 보자.
일단 public 상속일 때는 아래 코드는 문제없이 d.num을 접근할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
public:
int num;
virtual void SetNum(int);
};
class Derived : public Base
{};
int main()
{
Derived d;
d.num;
d.SetNum(3);
}
하지만 private 상속으로 바꾸게 되면 아래와 같이 컴파일 에러가 발생한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
public:
int num;
virtual void SetNum(int);
};
class Derived : private Base // private 상속
{};
int main()
{
Derived d;
d.num; // 컴파일 에러 발생
d.SetNum(3); // 컴파일 에러 발생
}
2-3. 결론
private 상속은 변환도 허용하지 않고 기본 클래스의 멤버들도 private으로 전부 바꿔버린다.
대체 왜 쓰는 걸까??
이에 대답은 책에서 아래와 같이 설명하고 있다.
D가 B로부터 private 상속을 받으면, 이것은 그냥 D 객체가 B 객체를 써서 구현되는 거라고 생각하세요.
스콧 마이어스,『effective C++ 제 3판』,곽용재,프로텍미디어(2015),p.280
위 코드로 설명하자면,
“Derived 객체는 Base 객체를 사용해서 구현된 것”이라 설명할 수 있을 것 같다.이를 통해
기본 클래스에서 물려받은 멤버들이 모두 private 멤버가 되는 이유도 설명이 되는 것을 알 수 있다.
왜냐하면,
Dervied 객체는 Base 객체를 사용해서 구현한 것이기 때문에 Base 멤버들을 Dervied 객체를 통해 사용하면 안 되기 때문이다.
예를 들어,
List 기능을 하는 CustomList를 만든다고 한다면 아래와 같이 private 상속을 통해 구현할 수 있을 것이다.
1
2
3
template<typename T>
class CustomList : private list<T>
{};
이러면 C++ STL의 list를 사용해서 CustomList를 구현하지만, CustomList는 C++ STL의 list의 일종은 아니게 된다.
3. 정리
private 상속은 public 상속과 다르게 객체를 구현하는 여러 방법 중 하나라고 정리할 수 있을 것 같다.
그리고
private 상속은 객체를 구현하는 여러 방법 중 하나이기 때문에 private 상속으로 구현하려는 것은 다른 방식으로도 구현할 수 있음을 잊지 말자!!