[C++] 상속 9) 객체지향의 문제점
객체지향의 문제점
다중 상속 (Multiple Inheritance)
다중 상속은 직계 부모 클래스를 하나 이상 가지는 클래스를 의미한다. 단일 상속과 마찬가지로 접근 제어자를 통한 상속 모드를 설정할 수 있고 프로그래머에게 새로운 문제를 안겨 준다.
- 두 개의 별개 부모 클래스로부터 이름은 같지만 서로 다른 메서드를 상속하는 문제
- 둘 이상의 서로 관련된 인접 부모 클래스를 상속 받는다면 사용할 메서드가 모호해지는 문제
다중 상속
#include <iostream>
using namespace std;
class Human {
public:
Human() {
cout << "Human Constructor" << '\n';
hAge = 0;
}
~Human() { cout << "Human Destructor" << '\n'; }
virtual void Walk() { cout << "Human Walk" << endl; }
private:
int hAge;
};
class Student : public Human {
public:
Student() {
cout << "Student Constructor" << '\n';
sAge = 0;
}
~Student() { cout << "Student Destructor" << '\n'; }
void Walk() override { cout << "Student Walk" << endl; }
void Talk() { cout << "Student Talk" << endl; }
private:
int sAge;
};
class Friend : public Human {
public:
Friend() {
cout << "Friend Constructor" << '\n';
fAge = 0;
}
~Friend() { cout << "Friend Destructor" << '\n'; }
void Walk() override { cout << "Friend Walk" << endl; }
void Talk() { cout << "Friend Talk" << endl; }
private:
int fAge;
};
class Hong : public Student, public Friend {
public:
Hong() {
cout << "Hong Constructor" << '\n';
hongAge = 0;
}
~Hong() { cout << "Hong Destructor" << '\n'; }
void Walk() override { cout << "Hong Walk" << endl; }
private:
int hongAge;
};
int main()
{
Hong hong;
cout << '\n';
hong.Walk();
hong.Student::Walk();
hong.Friend::Walk();
hong.Student::Talk();
hong.Friend::Talk();
// hong.Talk(); // 오류 발생!!
cout << '\n';
return 0;
}
Human Constructor
Student Constructor
Human Constructor
Friend Constructor
Hong Constructor
Hong Walk
Student Walk
Friend Walk
Student Talk
Friend Talk
40
main.cpp: In function ‘int main()’:
main.cpp:37:10: error: request for member ‘Talk’ is ambiguous
37 | hong.Talk();
| ^~~~
main.cpp:19:10: note: candidates are: ‘void Friend::Talk()’
19 | void Talk() { cout << "Friend Talk" << endl; }
| ^~~~
main.cpp:13:10: note: ‘void Student::Talk()’
13 | void Talk() { cout << "Student Talk" << endl; }
| ^~~~
Hong Destructor
Friend Destructor
Human Destructor
Student Destructor
Human Destructor
함수
위 UML과 코드에서 학생과 친구는 모두 사람에 속하고 Hong이라는 사람은 학생이자 친구가 될 수 있다.
그런데, 학생으로써 걷고 말하는 것과 친구로써 걷고 말하는 것은 서로 다른 기능이 구현되어 있다.
위 다중 상속의 경우 Hong이라는 사람은 필요한 상황에 따라 매번 다른 걷기(Walk)함수를 명시자("Student::Walk()" 혹은 "Friend::Walk()")를 통해 다른 작업을 수행해야한다.
또한, 말하기(Talk)는 Hong 클래스에 선언되어 있지 않기 때문에 명시자 없이 사용은 모호하기 때문에 불가능하다.
변수
위 예제는 '나이'라는 변수를 각 클래스가 가지고 있다. '사람'이라는 클래스의 나이와 '학생', '친구' 클래스의 '나이' 변수는 각자 메모리에 할당되는 독립된 변수이다.
- '학생', '친구', '사람' 클래스 모두 '나이' 변수를 할당한다.
- '학생'과 '친구' 클래스는 '사람'클래스를 상속 받는다.
- 모든 클래스의 각 변수는 메모리에 독립적으로 할당된다.
- 사용되는 클래스 개수만큼 '나이' 변수가 메모리에 할당된다.
이처럼 다중 상속은 코드의 유연성을 제공하지만 프로젝트의 규모가 커짐에 따라 관리하기가 힘들어질 수 있고 예상치 못한 오류를 만들 수 있다. (C#에 경우에는 다중 상속을 금지한다.)
가상 상속 (Virtual Inheritance)
위 과정에서 동일한 클래스를 상속 받아 발생하는 문제를 확인하였다. 이제 이를 해결할 수 있는 방법으로 가상 상속을 배우도록 하겠다.
우선 가상 상속은 'virtual' 키워드로 적용된다. 'virtual'을 짧게 요약하면, 부모 클래스의 정보를 자식 클래스에서 재정의할 수 있도록 하는 키워드로, 부모와 자식이 동일한 정보를 가지도록 중첩되는 현상을 방지하며 오버라이딩하여 새로운 정보로 변환할 수 있게 해준다.
가상 상속
#include <iostream>
using namespace std;
class Human {
public:
Human() {
cout << "Human Constructor" << '\n';
hAge = 0;
}
~Human() { cout << "Human Destructor" << '\n'; }
virtual void Walk() { cout << "Human Walk" << endl; }
private:
int hAge;
};
class Student : virtual public Human {
public:
Student() {
cout << "Student Constructor" << '\n';
sAge = 0;
}
~Student() { cout << "Student Destructor" << '\n'; }
void Walk() override { cout << "Student Walk" << endl; }
void Talk() { cout << "Student Talk" << endl; }
private:
int sAge;
};
class Friend : virtual public Human {
public:
Friend() {
cout << "Friend Constructor" << '\n';
fAge = 0;
}
~Friend() { cout << "Friend Destructor" << '\n'; }
void Walk() override { cout << "Friend Walk" << endl; }
void Talk() { cout << "Friend Talk" << endl; }
private:
int fAge;
};
class Hong : public Student, public Friend {
public:
Hong() {
cout << "Hong Constructor" << '\n';
hongAge = 0;
}
~Hong() { cout << "Hong Destructor" << '\n'; }
void Walk() override { cout << "Hong Walk" << endl; }
private:
int hongAge;
};
int main()
{
Hong hong;
cout << '\n';
hong.Walk();
hong.Student::Walk();
hong.Friend::Walk();
hong.Student::Talk();
hong.Friend::Talk();
// hong.Talk(); // 오류 발생!!
cout << '\n';
return 0;
}
Human Constructor
Student Constructor
Friend Constructor
Hong Constructor
Hong Walk
Student Walk
Friend Walk
Student Talk
Friend Talk
48
Hong Destructor
Friend Destructor
Student Destructor
Human Destructor
위 코드는 처음 작성된 '다중 상속' 코드에서 상속 관계에 'virtual' 키워드가 추가된 것이다. 여기서 'virtual' 키워드가 선언되면 가상 테이블이 생성된다. 가상 테이블은 간단하게 말하면 부모와 자식의 중복되는 정보 중 자식이 오버라이딩한 정보의 주소가 저장된 배열이다.

즉, 위 코드에서는 'virtual' 키워드를 통해 가상 클래스를 상속 받는다. 가상 상속이 진행되며 'Virtual Base Table'이 자동 생성되고 서로 중복되는 부모 객체를 하나로 통합한다. 이때, 가상 상속을 받은 자식 클래스는 가상 테이블 배열을 가지는 것이 아닌, 가상 테이블 배열의 head 원소의 주소 값을 가지게 된다.
참고자료
C++ 가상 상속(virtual inheritance)
1. 가상 상속(virtual inheritance) 이란??C++에선 다중상속을 지원합니다. JAVA는 다중상속을 막고 있지요.다중상속은 장점과 단점이 존재합니다.장점으론 객체지향의 상속성을 좀 더 유연하게 해주는
hwan-shell.tistory.com