[C++] OOP (5) Dynamic dispatch / Multiple Inheritance
객체지향 언어에는 같은 signature를 가진 함수를 여러 개 정의할 수 있다. (signature 가 다르면 다른 함수이다)
이를 function overloading 이라고 부르고, 여러 함수 중 어떤 함수가 호출될지는 컴파일 시간에 정해진다.
여기까지는 지난번에 배웠고..
이번에 살펴볼 function overriding 은 부모에서 정의된 함수를 자식이 새로 정의하는 작업이다.
함수를 사용할 때 어떤 함수가 불리는지는 런타임 시간에 정해진다.
즉, Dynamic dispatch를 사용한다.
런타임 시간에 불리는 함수가 결정되는 경우를 Dynamic dispatch 라고 부른다.
런타임 오버헤드가 있어 성능은 떨어질 수 있지만 코드를 훨씬 효과적으로 작성할 수 있어 상황에 맞춰서 사용한다.
C++ 에서는 Static dispatch / Dynamic dispatch 를 모두 제공한다.
Dynamic dispatch를 사용하려면 Receiver Object의 타입을 포인터 또는 레퍼런스 타입으로 맞춰 놓고, 함수를 virtual 키워드와 함께 정의해야 한다.
위와 같은 코드를 실행하면 ShowEmployees() 함수에서 static dispatch를 통해 Employee 클래스의 GetInfo() 함수가 호출된다.
여기서 Dynamic dispatch를 사용해보자.
오버라이드 할 함수에 virtual 키워드를 붙이면 자식 클래스에서 부모 클래스의 함수를 오버라이드한다. (이 때 자식 클래스에서는 virtual 키워드를 붙여도 되고 안 붙여도 된다)
위와 같이 작성하면 출력 시 자식 클래스의 GetInfo() 함수를 호출해 의도대로 출력됨을 확인할 수 있다.
앞에서도 언급했듯, Dynamic dispatch를 사용할 때는 Receiver Object를 포인터 타입이나 레퍼런스 타입으로 정의해야 함에 주의하자.
자바에서는 함수를 오버라이드 할 때 @Override 애너테이션을 사용했었다.
C++에서도 비슷한 기능이 있다. override 키워드를 사용하면 된다.
부모의 메서드를 자식이 오버라이드 할 때 메서드의 정의 부분에서 override 키워드를 붙이면 된다.
자바에서와 마찬가지로 override 키워드를 사용한다고 해서 출력 결과가 바뀌거나 하지는 않는다.
컴파일러에게 해당 함수를 오버라이드한다는 정보를 전달하고, 컴파일러가 제대로 오버라이드됐는지 확인할 수 있도록 한다.
override 키워드를 사용해 코드의 가독성을 높이고 컴파일러에게 힌트를 제공한다.
자바에서도 Static dispatch와 Dynamic dispatch를 모두 지원하지만, C++의 구현 방식과는 좀 다르다.
자바에서 private 메서드는 Static dispatch, protected public 메서드는 Dynamic dispatch로 고정되어 동작한다.
C++은 기본으로 Static dispatch를 사용하지만, 위에서 확인 한 것 처럼 virtual 키워드를 통해 Dynamic dispatch로 변경할 수 있어 자바보다는 더 유동적이라고 할 수 있다.
C++ 에서 Dynamic dispatch가 어떻게 구현되는지 살펴보자.
virtual 메서드를 포함한 클래스의 인스턴스가 만들어 질 때 virtual pointer (VPTR) 가 인스턴스의 멤버 변수로 포함된다.
각각의 클래스는 VTABLE을 가지고 있고, VTABLE은 클래스의 virtual function들을 포함하고 있다.
VPTR은 VTABLE을 가리키고 컴파일러는 VPTR의 Receiver Object가 가리키는 VTABLE을 확인하고 VTABLE의 함수를 호출한다.
멤버 변수 내부의 VPTR은 개발자가 조작 할 수 없다고 생각하면 된다. (sizeof() 함수로도 알아낼 수 없다)
클래스마다 VTABLE이 있고, 클래스의 인스턴스마다 VPTR이 있어 VPTR이 적절한 VTABLE을 가리킨다.
위의 예시는 virtual function이 GetInfo() 하나밖에 없지만, virtual function이 많아지면 컴파일러가 코드를 변환할 때 vptr[1]() 처럼 인덱스를 적절하게 설정한다.
Dynamic dispatch를 사용 할 때는 무조건 VTALBE과 VPTR이 함께 사용된다.
뭐가 Dynamic dispatch인지 확실히 이해하자.
생각해 보면, 위의 예시에서 사용하는 Employee 클래스로는 인스턴스를 만들 일이 없다.
Employee 클래스는 다른 클래스를 정의할 때 사용되는 상속을 위한 클래스인데.. 이런 클래스를 따로 정의할 수는 없을까?
상속을 위한 클래스로 abstract class가 있다.
자바에서는 abstract 키워드가 있어 쉽게 추상클래스를 정의할 수 있지만, C++에서는 다른 방법을 사용한다.
virtual 키워드와 GetInfo() 함수의 = 0; 부분에 집중하자.
위의 두 가지 요소를 가진 함수를 pure virtual function이라고 부르고, pure virtual function이 하나라도 있는 클래스는 abstract class로 정의된다.
추상클래스로는 인스턴스를 만들 수 없고, 추상클래스를 상속받은 클래스에서 pure virtual function을 정의하지 않으면 그 클래스 역시 추상클래스가 돼 인스턴스를 만들 수 없다.
virtual function을 하나 이상 가진 클래스를 polymorphic class 라고 부른다.
polymorphic class 를 다룰 때는 base class의 destructor를 virtual 키워드와 함께 정의하자.
Developer 클래스에서 delete 를 수행하면 static dispatch로 Employee 클래스의 destructor 만 호출하기 때문에 Developer 클래스의 dev_name 에서 Memory leak이 발생한다.
따라서, Employee 클래스의 desturctor를 virtual로 설정해 dynamic dispatch를 사용하고, Developer 클래스와 Employee 클래스의 destructor를 모두 호출할 수 있도록 하자.
(자식 클래스의 destructor를 호출할 때 부모 클래스의 destructor도 호출된다)
C++ 은 다른 객체지향 언어들과는 다르게 다중 상속을 지원한다.
상속받을 클래스를 , 로 계속해서 작성하면 된다. (접근 제어자도 각각 설정할 수 있다)
상속받은 클래스는 두 가지 부모 모두 가리킬 수 있다.
destructor는 역순으로 불린다.
Developer 클래스가 Employee, Citizen 클래스를 상속받는다.
Developer 클래스가 delete 되면 Developer -> Citizen -> Employee 순으로 destructor가 호출된다.
Citizen : Star { }
Developer : Employee, Citizen { }
위와 같은 경우는 Developer 클래스가 delete 될 때
Developer -> Citizen -> Star -> Employee 순으로 destructor가 호출된다.
다중 상속 관련 문제 중 Diamond Problem이 있다.
상속받는 멤버 변수의 이름이 같으면?
객체지향언어마다 해결 방법이 다르다.
위의 경우 C++ 에서는 name을 사용하려고 할 때 컴파일 에러를 뱉는다.
파이썬에서는 부모의 순서를 매겨서 우선순위가 더 높은 요소를 사용한다.
자바는 애초에 다중 상속을 지원하지 않는다.
C++에서 다중 상속을 사용할 때 선택의 기로에 서지 않으면 오류를 뱉지 않는다고 생각하면 된다.
어떤 걸 사용해도 상관 없는 경우 문제되지 않는다.
자바에서는 super 키워드를 통해 부모의 요소에 접근하는데, C++에서는 super를 사용하지 않는다.
대신 namespace로 B::foo() 이런 식으로 부모 요소에 접근한다.
'Programming Language > C++' 카테고리의 다른 글
[C++] Design Pattern (0) | 2022.11.24 |
---|---|
[C++] 정리 (1) (0) | 2022.11.23 |
[C++] OOP (4) Type Casting (0) | 2022.10.19 |
[C++] OOP (3) Inheritance / 연산자 오버로딩 (1) | 2022.10.12 |
[C++] C++과 Java (1) | 2022.10.05 |
댓글
이 글 공유하기
다른 글
-
[C++] Design Pattern
[C++] Design Pattern
2022.11.24 -
[C++] 정리 (1)
[C++] 정리 (1)
2022.11.23 -
[C++] OOP (4) Type Casting
[C++] OOP (4) Type Casting
2022.10.19 -
[C++] OOP (3) Inheritance / 연산자 오버로딩
[C++] OOP (3) Inheritance / 연산자 오버로딩
2022.10.12