가상 함수
- 앞 시간에 배운 다형성을 구현하는 방법 중 Override는 다형성 구현을 위해
- 상속의 관계에서 부모의 가상 함수를 자식 클래스에서 재정의 하는 방식을 이용하였다.
- 어떤 타입의 객체가 들어올지 모르는 상태에서
- 함수를 정적 바인딩(컴파일 시에 결정)이 아닌
- 동적 바인딩(함수 실행 시)으로 만들어 주기 위해서 가상 함수로 사용
가상 함수는 어떤 방식으로 동적 바인딩을 가능하게 만들어 주는 것일까?
가상 함수의 특징과 작동원리에 대하여 실습을 통해 알아보자.
기본 세팅
- 부모 클래스에 함수 3개 만들기 ( 일반 함수 1개 가상 함수 2개)
- 자식 클래스 2개 만들기 - ( 일반 함수 재정의 , 가상 함수 1개 재정의 )
부모 A클래스
- 일반 함수 info ( )
- 가상 함수 print( )
- 가상 함수 print2( )
자식 B 클래스 : A 상속
- 일반 함수 info( )
- 가상 함수 print( )
자식 B 클래스 : A 상속
- 일반 함수 info( )
- 가상 함수 print2( )
3개의 함수 ( info , print , print2 )를 호출하는 함수를 만든다.
- A& - 부모의 타입으로는 자식 a, b, c 다 올 수 있다
- ref로 A타입 B타입 C타입 객체를 각각 넣어서 함수 호출 - 출력 테스트
각 타입별 함수 호출 - 출력 테스트
## Check Point
일반 함수 info( ) 는
- 재정의 여부 상관없이 다 A :: info( ) 호출
- 일반 함수는 들어오는 데이터 타입에 상관없이 담고 있는 그릇의 데이터 타입에 맞춰 함수 호출
- A타입의 객체를 받은 거라서( A상속받아 만든 B, C포함) A타입의 info ( ) 호출됨
가상 함수 2개 중 - 재정의 된 것은 그 객체의 함수 호출
- B 클래스에서는 Print( ) 재정의 ----> B :: Print 출력 확인
- C 클래스에서는 print2( ) 재정의 ----> C :: Print2 출력 확인
가상 함수 2개 중 - 재정의 되지 않은 것은 부모의 것 그대로 호출
- B 클래스에서는 Print2( ) 는 재정의하지 않았다. - A :: Print2 출력 확인
- C 클래스에서는 Print( ) 는 재정의하지 않았다. - A :: Print 출력 확인
일반 함수는 자식 클래스에서 재정의되어있어도 부모의 것으로 출력되는 부분도 check
직관적으로 비교하기 좋게 코드 순서를 조정해서 출력해 보았다.
A, B, C 타입 모두 일반 함수인 info ( )는
- A 타입 A :: info( )으로 모두 출력됨을 확인
가상 함수가 재정의된 부분은
- 그 데이터 타입에 맞춰 출력됨을 확인 - B :: print( )와 C :: print2 확인
가상 함수가 재정의되지 않은 부분은
- 부모인 A타입의 것이 출력됨을 확인 - B객체 A :: print2( )와 C 객체 A :: print 확인
일반 함수 info( ) 호출 시
일반 함수는 들어오는 데이터 타입에 관계없이 그것을 담는 그릇 - A타입 -에 맞춰서 호출!
컴파일 시에 정해진다 - 정적 바인딩
가상 함수 print ( ) 호출 시
그릇에 담겨있는 데이터 타입에 준하여 작동
어디의 print 함수 호출될지 몰라서 컴파일 시에 바인딩 결정할 수 X
유보한다 - 동적 바인딩
클래스에 가상 함수가 하나라도 있을 경우
- _vfptr 이라는 가상 변수가 ( 멤버 변수로 ) 자동으로 만들어짐 – 함수의 포인터 변수
- _vfptrArray 라는 가상 함수 테이블이 자동으로 만들어짐 – 함수의 포인터 배열
- 부모의 가상 함수 테이블에 저장되어있는 가상 함수가 2개였으므로
- 상속받은 b, c클래스에도 가상 함수 테이블 안에 가상 함수 2개 만들어짐
정리
일반 함수는
- 그릇의 데이터 타입의 함수 호출 – 고정
- 인자로 A, B, C 타입 어떤 타입이 들어오든 간에 무조건 담는 그릇인 A 클래스 info( ) 호출!
- 정적 바인딩 – 컴파일 타임(실행 파일을 만들 때)에 바인딩이 결정된다.
- ref . A :: info( ); - A에 있는 info 함수를 호출할거야라고 받아들인다.
가상 함수는
- 그릇의 데이터 타입이 아닌 담겨있는 타입으로 호출
- 재정의 되지 않았으면 부모의 함수 그대로 사용
- 어떤 데이터 타입이 들어올 줄 모른다 - 어떤 데이터 타입의 info( ) 함수가 호출될지 모른다.
- 동적 바인딩 - 컴파일 타임(실행 파일을 만들 때 ) 바인딩 결정 못한다. - 바인딩 유보한다.
가상 함수의 작동원리
알기 위해서 먼저 함수 포인터와 함수 배열 Check
함수 포인터
- 함수도 메모리에 위치한다 - 주소 값을 가지고 있다.
- 함수명은 함수를 저장하고 있는 공간의 주소 값 그 자체
- 주소 값 저장 가능하다.
15번 Line – 함수 포인터 변수 - 함수의 주소를 저장하는 변수 pfunc
15번 Line - 여기에 포인터 연산자( * )를 해주면 공간을 의미 - 그 공간에는 함수가 저장되어있다.
함수 포인터 배열
- 함수 포인터들의 배열
- 함수의 주소를 저장하고 있는 변수 pfunc들을 저장하고 있는 배열
21번 Line -- pfuncArray는 함수의 주소를 저장하고 있는 pfunc들이 위치하고 있는 배열
21번 Line -- 여기에 포인터 연산자( * )를 해주면 공간을 의미 - 그 공간에는 *pfunc 저장되어있다.
22번 Line -- 그 배열 [ 0 ] 번째에 add - 함수명(주소 값)을 [ 1 ] 번째에 sub - 함수명(주소 값)을 대입
( 함수명은 자체가 주소 값이므로 &생략 가능 – 써주면 좋긴 하다 )
25번 Line -- 배열 0번째에 있는 함수 - 주소 값으로 add 함수 호출!
26번 Line -- 배열 1번째에 있는 함수 - 주소 값으로 sub 함수 호출!
이처럼
- 함수가 있고 - add( ) , sub( )
- 함수의 주소를 저장하는 변수가 있고 - pfunc - 함수 포인터 변수
- *pfunc ( 함수의 주소를 저장하는 변수 )를 배열의 형식으로 가지고 있는 테이블이 있고- *pfuncArray[ ]
- 이 배열의 몇 번째 공간( * ) 호출 - 그 공간에 저장되어있는 함수 주소 값으로 - 함수 호출하는 것
이와 같은 형식으로!
- 가상 함수가 있고 - virtual void print( ) , virtual void print2( )
- 함수의 주소를 저장하는 변수가 있고 - _vfptr - 가상 함수 포인터 변수
- _vfptr ( 가상 함수의 주소를 저장하는 변수 )를 배열의 형식으로 가지고 있는 가상 함수 테이블이 있고- ref.vfptr[ ]
- 이 배열의 몇 번째 공간 호출 - 그 공간에 저장되어있는 가상 함수 주소 값으로 - 가상함수 호출하는 것
- pfunc --> _vfptr
- *pfuncArray[ 0 ] ( ) --> ref._vfptr[ 0 ]( ) ;
## Check Point
- 가상 함수가 클래스에 하나라도 만들어지면,
- 가상 함수 테이블 - ((( ref . _vfptr[ ] ))) 이 자동으로 만들어진다.
- 가상 함수의 주소 값을 저장하는 변수 - ((( _vfptr ))) - 이 자동으로 만들어진다.( 자동으로 )
review
virtual 만 붙여주면 가상 함수가 되고
가상 함수는 들어오는 데이터 타입에 맞춰
함수를 호출해 준다.
사용해봤던 개념이라 이해하기도 사용하기도 쉬웠다.
But.
함수를 가상 함수로 만들어주면 왜 그렇게 되는 것인가?
몰랐다.
이해한 내용을 정리해보자면
가상 함수가 하나라도 만들어지면
가상 함수 주소를 가리키는 변수인 함수 포인터 변수가
자동으로 생긴다 _vfptr
그 변수들을 저장하는 배열인 함수 포인터 배열 - 테이블이
자동으로 생기고 여기에 저장된다. _vfptrArray
이 가상함수 테이블은 상속받는 클래스 모두에게 똑같이 생긴다.
다만 자식 클래스에서 가상 함수를 재정의하면
테이블에 저장되어있던 부모의 가상 함수 대신
덮어쓰기의 개념으로 테이블에 저장된다.
함수 호출 시
이렇게 각 클래스마다 가지고 있는 가상 함수 테이블에 저장되어있는
그 클래스의 가상 함수가 호출되는 것이다!
A:: B:: C:: 의 가상 함수들 호출
재정의한 부분 있으면 그 클래스의 것으로
재정의하지 않았으면 부모의 것 그대로 호출
흠. 이해한 내용을 글로 설명하려니까 어렵긴 하다.
이런 부분도 연습이 필요할 것 같다.ㅎㅎ
가상 함수의 작동원리를 알 수 있는
좋은 시간이었다.
'Back-end > C++' 카테고리의 다른 글
22. 04. 01 - 몬스터 전투 실습 ( 상속, 오버 라이딩, 가상 함수, 추상 클래스) (0) | 2022.04.10 |
---|---|
22. 03. 31 - 몬스터 전투 실습 ( 오버로딩을 통한 다형성, 파일분할 ) (0) | 2022.04.09 |
22. 03. 29 - 다중 상속, 다형성 : Overloading, Override (0) | 2022.04.06 |
22. 03. 28 - 포함 ( Compositon, Aggregation ) (0) | 2022.04.06 |
22. 03. 25 - 상속( 접근 제어자, 업 캐스팅, 다운 캐스팅 ) (0) | 2022.04.03 |