이 게시글은
서울대학교 데이터사이언스대학원
조요한 교수님의
데이터사이언스 응용을 위한 컴퓨팅 강의를
학습을 위해 재구성하였습니다.
Multiple Inheritance
Multiple Inheritance란 하나의 derived class가
여러 개의 base class를 상속받게 해주는 것이다
저번 시간과 동일하게 포켓몬으로(..)
예시를 한 번 보자
전기속성을 띄는 포켓몬은 electricLevel이라는 attribute를
불속성을 띄는 포켓몬은 flameLevel이라는 attribute를
갖고 있다
따라서 ElectricPokemon이라는 클래스와
FirePokemon이라는 클래스를 또 만들려고 한다
그럼 Pikachu는 포켓몬이자 전기속성을 띄는 포켓몬이므로
BasePokemon class와 ElectricPokemon class를
상속받게 되는 것이다
마찬가지로 파이리도
BasePokemon class와 FirePokemon class
2개를 상속받게 된다.
전체 구현은 위와 같다
BasePokemon에는 name과 hp와 같은
기본적인 것들만 구현하고
enum Type은 바깥에 구현해뒀다
그런 다음 ElectricPokemon class와
FirePokemon class를 구현해줬다
그런 다음 이제
BasePokemon과 ElectricPokemon을
상속받을 Pikachu 클래스를 구현해보자
위와 같이
Class Pikachu : public BasePokemon, public ElectricPokemon
이렇게 써주면 Pikachu는 2가지 클래스를 상속받게 되는 것이다
constructor에서는
BasePokemon과 ElectricPokemon도
초기화해준다
그런 다음 main함수에서
Pikachu object를 생성한뒤
BasePokemon의 method나
ElectricPokemon의 method를
실행시키면 정상적으로 되는 것을 볼 수 있다
Charmander class도 피카츄와
비슷하게 구현해주면된다
이렇게 여러 개의 class를 상속받을 때
constructor의 호출 순서에 대해 알아보자
부모 class들의 constructor가 먼저 호출이 되는데
그 순서는 위에 있는
BaseClassList에 작성이 되어있는
순서대로 호출이 된다
class Charmander : public BasePokemon, public FirePokemon
과 같이 BaseClassList가 구현되어있으므로
BasePokemon이 먼저
그 다음 FirePokemon이 호출이 된다
constructor에 적혀있는 순서는
아무런 상관이 없다
그렇다면 Destructor의
호출 순서를 한 번 알아보자
Destructor의 call은 constructor call과는
반대 방향으로 호출된다
우선 derived class의 destructor가
먼저 호출되고
BaseList에 기록된 반대 방향으로 호출된다
이제 이렇게 여러가지 class를 상속받은
class의 memory layout을 살펴보자
Pikachu는 상속을 받은 class이므로
vptr이 가장 위에 있고
그 다음에는 BasePokemon의 attribute들
그다음은 ElectricPokemon의 attribute들이
있는 것을 볼 수 있다
그다음 가장 마지막에
Pikachu에 특화된 method인
cry가 있는 것을 볼 수 있다
중간 padding은 저번 시간에도 정리했지만
한 번 메모리에 접근할 때
cpu는 뭉텅이(?)로 가져오기때문에
그 효율성을 위한 값이라고 보면 된다
그런데 이렇게 한 클래스가
여러 개의 클래스를 상속받으면
발생할 수 있는 문제는 어떤 것들이 있을까?
위 코드 예시를 통해 한 번 살펴보자
피카츄와 파이리의 혼종인
Pikamander를 만들었다
불도 쓰고 전기도 쓰는
초특급 울트라 초강력 포켓몬이라고한다
그래서 이를 구현하기위해
Pikamander라는 class를 새로 만들고
BasePokemon, ElectricPokemon, FirePokemon
3가지의 포켓몬을 상속받았다
그런 다음 main함수에서
Pikamander를 정의하고
getType을 호출했다
그럼 이 과정에서 에러가 뜨는데 그 이유는
getType method는
ElectricPokemon class에도
FirePokemon class에도 존재하기때문에
두 클래스에서 어떤 메소드를
호출해야할지 몰라 에러가 뜨는 것이다
이런 문제를
Ambiguity라고 한다
같은 이름을 가진 method나 attribute가
muliple parents에 있을 때
발생하는 것이다
메모리 레이아웃을 보며
어떻게 Ambiguity가 발생하는지
한 번 이해해보자
이름이 동일한 attritbute를 갖고있는
2개의 클래스를 상속받았다는 것은
동일한 attritbute에 대한 copy가 2개가
생긴다는 의미이다
따라서 위 메모리 레이아웃을보면
type이라는 attribute는 상속이 2번 되었고
서로 다른 2번의 copy가 생겼다
그래서 getType을 사용하면
둘 중에 어떤 것을 호출하면 되는지 몰라
error가 발생하는 것이다
그렇다면 이런 Ambiguity를
어떻게 해결할까?
단순하게 생각하면
pikamader.FirePokemon::type
이런 식으로 어디서 가져온 attribute인지
적어주는게 필요할 것이다
아니면 조금 더 근본적으로
ambiguity를 피할 수 있도록
pikamander 클래스 안에
getType method를 정의해서
명확하게 어떤 값을 불러올지
정의를 해줄 수도 있다
아니면 정말 더 근본적으로
애초에 이런 문제 자체가 발생한다는 것 자체가
application의 design에 flaw가
있다는 것을 암시하기 때문에
class들의 구조를 아예 새로
refactoring하는 방법이 있다
따라서 이런 근본적인
design적 flaw를 없애기위해
나타난 것이 바로
Multi-Level Inheritance이다
Multi-Level Inheritance
그냥 모든 class를 다 상속받는게 아닌
계층 구조를 갖도록 상속받게 해주는 것이다
Pikachu는 ElectricPokemon만
ElectricPokemon은 BasePokemon을 상속받게끔
여러 계층으로부터 상속이 이어지는거라
multi-level inheritance라고 부른다
multi-level inheritance의
구현을 살펴보자
BasePokemon class를 정의해주고
아까는 밖으로 나와있던
enum Type을 BasePokemon의
attribute로 넣어준다
ElectricPokemon을 정의하며
BasePokemon을 상속받도록 해준다
BasePokemon을 초기화하면서
type에 ELECTRIC을 넣어준다
FirePokemon을 정의하며
BasePokemon을 상속받도록 해준다
가장 하위 class인 Pikachu의 구현을 보자
Pikachu는 ElectricPokemon을 상속받게 해준다
BaseClass -> ElelctricPokemon -> Pikachu
그럼 ElectricPokemon은 여기서
intermediate class가 된다
Pikachu의 constructor에서는
상속받는 class인
ElectricClass의 constructor만 호출해준다
그럼 ElectricClass가 호출되면서
그 Parent Class인 BasePokemon class도
같이 호출되게된다
Charmander class도 동일하게 구현해준다
그런 다음 main함수에서
Pikachu와 Charmander를 선언해서
여러 method나 attribute들을 호출해보자
우선 pikachu의 getType을 호출하면
getType은 BaseClass밖에 정의가
안되어있는 method이기 때문에
BaseClass의 getType이 호출된다
electricLevel은
ElectricPokemon에 있는
attribute이므로 거기에 있는게
호출이 된다
마지막으로 cry는 Pikachu에만 있는
method이기 때문에
Pikachu class에서 가져와서 호출이 된다
constructor call의 구조를 잘 보자
constructor의 호출은
상속받는 것의 역순이므로
BasePokemon이 가장 먼저 호출되고
그다음 ElectricPokemon -> Pikachu의 순서로
호출이 된다
위의 ppt의 코드 예시를 잘 보자
또 전기와 불을 모두 쓰는
초특급 혼종 포켓몬을 구현하려고했다
그래서 Pikamander라는 class를 새로 만들고
ElectricPokemon과 FirePokemon을
상속받게 해줬다
그런 다음 main함수에서
pikamander.getType()을 호출해주면
compile error가 뜨는 것을
확인할 수 있다
왜 이런 컴파일 에러가 생길까?
에러 메세지를 자세히 보면
getType이 multiple base-class에서
발견된다고 한다
한마디로 Pikamander가 상속받는
ElectricPokemon과
FirePokemon모두
type을 갖고 있어서 다시 한 번
ambiguity가 발생한 것이다
이런 문제를 바로
diamond problem이라고 한다
위 diagram을 보면 알겠지만
BaseClass가 있고
이를 상속받는 2개의 Intermediate class
그리고 그 2개의 Intermediate Class를
모두 상속받는 Derived Class에서
같은 name을 가진 method 혹은 attribute를
호출하면 ambiguity가 발생하는 것이다
위 memory layout을 보면
Pikamander에는
ElectricPokemon의 attribute들
그리고 FirePokemon의 attribute들이
저장이 되어있고
name, hp, type 변수들이
2개씩 copy가 되어 저장되어있는 것을
확인할 수 있다
이를 해결해주는 방법이 바로
Virtual Inheritance이다
virtual inheritance의 목적은
base class를 오직 한 번만
copy해주기 위함이다
위의 ppt에서 구현을 자세히 보자
우선 BasePokemon에서의 enum Type에
ELECTRICFIRE도 추가해준다
그런 다음
ElectricPokemon에서 BasePokemon을 상속할 때
virtual를 붙여준다
FirePokemon도 마찬가지
그런 다음 Pikamander를 초기화 할 때
BasePokemon, ElectricPokemon, FirePokemon
세 개를 모두 초기화해준다
이전과 다른 점은
virtual이라는 키워드를 썼다는 점과
Pikamader class에서
base class까지 constructor에
구현을 해줬다는 점이다
이럴 경우에는 어떻게
constructor call이 되는지 살펴보자
우선 Pikamander의 constructor에 진입해서
BasePokemon으로 가장 먼저 가게 된다
그 다음 ElectricPokemon의 constructor를 호출해주는데
이때 ElectricPokemon의 constructor 안에
BasePokemon이 있지만
virtual하게 상속을 받았기 때문에
BasePokemon의 constructor를 또 부를필요가
없다고 판단하여 skip하게 된다
그 다음 FirePokemon에 가게 돼서
똑같이 FirePokemon의 constructor를 호출하고
그 안에 있는 virtual로 선언된 BasePokemon의
constructor 호출은 skip하게 되는 것이다
따라서 결론적으로
BasePokemon의 constructor는
1번만 불려지게 되는 것이다
virtual inheritance를 할 때의
메모리 레이아웃을 살펴보자
virtual inheritance를 하게 되면
virtual BaseClass에 있는 것들은
딱 한 개만 생성이 된다
Pikamander의 Layout을 살펴보면
BasePokemon class의 attribute인
name, hp, type은 1개만 있고
그 다음 electricLevel
그 다음 flameLevel이 있는 것을
확인할 수 있다
Abstract Class
이제 특수한 class들을 살펴보자
Abstract Class의 목적은
이 클래스 자체를 상속받아서
다른 class들이 쓸 수 있게 해주는 것이
주요 목적이다
이게 무슨 소리냐하면
결국 상속받을 class를 만들어주는
일종의 청사진같은 존재라는 뜻이다
그래서 abstract class는
instance화를 시키지 못한다
위의 ppt에서 BasePokemon의
class 구현을 잘 보자
가장 마지막 attack method에서
virtual void attack(BasePokemon& opponent) = 0;
와 같이 구현된 것을 확인 할 수 있다
저렇게 구현된 함수를
pure virtual function이라고 하는데
pure virtual function이 1개만 있어도
abstract class로 정의된다
그렇다면 이 BasePokemon이라는 class는
abstract class이니 object화를 시킬 수 없다
따라서 무조건 상속을 받아서만 사용할 수 있는데
상속을 받을 때, pure virtual function인
attack은 반드시 override를 통해서 구현해줘야만한다
만약 override로 attack을 구현해주지 않았다면
그 상속받은 class도 abstract class로 정의되어
instance화를 시키지 못한다
abstract class의 모든 method들이
다 abstract일 필요는 없다
최소 1개 이상의
pure virtual function만 있으면 된다
abstract class를 상속받아서
pure virtual function이외의
다른 method들을 상속받지 않았다면
그건 abstract class에 있는대로 구현된다
abstract class를 사용하는 이유는
앞에서도 말했듯이
다른 class들의 blueprint역할을 하기 위해서다
어떤 함수의 definiton을
알려주기위한 용도로 사용된다
또한 각각의 class가 각각의 class에
특화되게 구현해주는걸 가능하게하는데
이 또한 polymorphism과 관련이 있다
Friend Class
Friend Class란
다른 class의 Private 혹은 protected member에
special한 접근 권한을 주는 것이다
위의 예시를 잘보자
Pikamander Class를 정의하고
그 안에서 Pikachu를 friend class로 정의했다
그럼 Pikachu는
Pikamander의 private과 protected member에도
접근을 할 수 있도록 허용해주는 것이다
하지만 이런 기능이
객체지향에서 추구하는 encapsulation을
일부 깨뜨리는 것인데
Pikamander가 아닌 Pikachu에서
protected나 private을
접근할 수 있도록 권한을 줬으니
encapsulation principle을
약간 희생하게 되는 것이다
결과적으로 module화가 덜 되는 것이도
서로서로 coupling이 되는 결과를
초래할 수도 있다
마지막으로 Friend Function을 보고
이번 수업내용정리는 마무리해보려고한다
friend function은 어떤 특정한
function에게 접근 권한을 주는 것이다
위의 ppt예시를 잘 보면
osstream& operator<<는
Pikamander의 member function이 아니다
하지만 Pikamander 안에 함수 정의가 포함되어 있다
앞에 friend라는 키워드를 붙여주었다
그럼 저 operator overloading function에게
Pikamander의 private, protected members에
접근할 권한을 주겠다는 뜻이다
그러고 저 operator overloading 함수는
global scope의 어느 곳에
자세한 구현부가 정의되어있을 텐데
이 때 pikamander에 정의되어있는
private이나 protected한 member들에
접근이 가능하다
'강의 > computer science' 카테고리의 다른 글
[computer science] Binary Tree, Max Heap (1) | 2024.11.08 |
---|---|
[Computer Science/c++] Type Casting과 Exception Handling (0) | 2024.10.26 |
[computer science] c++의 class와 inheritance(상속), class substitution, virtual function, dynamic binding (1) | 2024.10.18 |
[computer science] c++의 Copy/Move semantics, Static (0) | 2024.10.15 |
[computer science] c++의 operator overloading/연산자 구현하기 2편 (0) | 2024.10.14 |