본문 바로가기
Develop/Cpp

[C++] 가상함수테이블(VTable)과 virtual

by Tarra 2023. 2. 28.

 

 

 


개인 공부 후 자료를 남겨놓기 위한 목적이므로,
생략되거나 오류가 있을 수 있음을 알립니다.

 

 

 


오버라이딩 (overriding)

가상함수테이블과 virtual을 알기 위해서 오버라이딩을 한번 짚고 가도록 하자.

 

 

코드를 보면 Child는 Parent를 상속받고 있고, 

이미 Parent 클래스에서 func() 함수를 선언했음에도 Child 클래스에서 같은 이름의 func() 함수를 또 선언하고 있다.

 

이처럼  상속 받은 함수를 자식 객체 내에서 새롭게 정의하는 것을 오버라이드라고 한다.

( 이때 return 타입과 함수 인자 구성은 동일해야함 )

 

위의 함수도 실행해보면 각자의 func() 함수가 잘 출력되는 것을 볼 수 있다.

 

 

문제는 여기서 부터 시작된다.

 

위에서 Parent 클래스로 선언된 p 포인터 변수에 Child 객체의 주소를 넣고 func() 함수를 호출하면 Child 클래스의 func()가 호출되는 것이 아닌 Parent 클래스의 func()이 호출된다.

 

 

이러한 현상은 정적 바인딩 때문에 나타난다.

컴파일러는 컴파일 타임에 p 변수가 Parent 타입임을 보고,  p변수에 func() 함수를 바인딩하게 되고

이상태로 빌드가 끝나 Parent의 func() 함수가 호출 되는 것이다.

 

대부분의 함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환된다.

이것을 정적 바인딩(static binding) 또는 초기 바인딩 (early binding)이라고한다.

C++ 에서는 가상 함수가 아닌 멤버 함수는 모두 이러한 정적 바인딩을 하게 됩니다.

- TCP School (http://www.tcpschool.com/cpp/cpp_polymorphism_virtual)

 

다시 말해 컴파일 타임에 이미 어떤 함수를 쓸지 정해지기 때문에 Parent로 선언된 포인터 변수 p는 Parent 클래스의 func()를 호출하는 것.

 

비슷한 경우로 다음과 같은 상황도 존재한다.

 

 

Child 클래스가 Parent 클래스를 상속 받았지만, Parent 클래스의 func() 뿐만 아니라 오버로딩된 함수 func(int a)도 호출되지 않는데

이를 함수가 숨었다고 해서 Hiding이라고 한다.

 

이러한 문제는 객체 지향 프로그래밍의 특성중 하나인 다형성을 무시하게 만든다.

 

이제 이를 해결할 수 있는 방법을 알아보도록 하자.

 

 


Virtual ( 가상 함수 )

이를 해결하기 위해서 C++에서는 `virtual`이라는 키워드를 함수 선언시 붙임으로서 가상 함수를 만들어 문제를 해결한다.

 

C++에서 가상 함수는 파생 클래스에서 재정의 할 것으로 기대되는 멤버함수를 의미한다.

이러한 가상 함수는 자신을 호출하는 객체의 동적 타입에 따라 실제 호출할 함수가 결정된다.

(런타임시 함수의 다형성을 구현하는데 사용된다.)

- TCP School (http://www.tcpschool.com/cpp/cpp_polymorphism_virtual)

 

함수 선언시 다음의 코드와 같이 virtual을 붙임으로서 해당 함수가 가상함수임을 컴파일러에게 알려주게 된다.

 

(virtual 사용시에 해당 함수는 런타임시 함수를 바인딩하는 동적 바인딩을 사용하게 된다.)

 

 

 

 


가상함수 테이블 ( virtual function table, vtbl )

가상함수테이블 (VTable)은 C++에서 가상함수를 구현하기 위한 내부적인 메커니즘으로, 가상함수가 호출될 때 해당 함수의 주소를 저장하고 있는 테이블이다.

 

VTable은 각각의 클래스마다 하나씩 생성되며, 클랙스의 객체가 생성될 때마다 해당 클래스의 VTable 포인터가 객체 내부에 저장되게 된다.

 

쉽게 말해 가상함수 테이블은

가상 함수 포인터들을 모아둔 배열이라고 할 수 있으며 이를 이용하여 컴파일러는 오버라이드 함수를 구분한다.

 

컴파일시 가상함수가 정의된 클래스가 있다면 가상함수테이블이 만들어지며 이는 "rdata" 영역에 기록된다.

 

"rdata" 영역은 읽기 전용 데이터 (read-only data)를 저장하기 위한 메모리 영역으로,

프로그램 실행 중에 수정되지 않아야 하는 상수 데이터, 문자열 리터럴, 가상함수테이블등의 데이터가 저장된다.

일반적으로 코드와 데이터 모두 읽기 전용으로 설정되어 있기 때문에 보안과 안정성 측면에서 이점이 있다.

가상함수테이블은 프로그램 실행 중에 수정되지 않으며, 클래스의 정의가 수정되지 않는 한 내용이 항상 동일하기 때문에

"rdata" 영역에 저장된다.

 

 

오버라이딩 된 함수를 각각의 가상함수테이블에서 가져다 쓴다.

 


가상 소멸자 ( Virtual Destructor )

일반적인 상속의 경우엔 다음과 같은 프로세스를 거쳐 생성자와 소멸자가 호출되게 된다.

 

하지만 다음과 같이 부모 클래스를 이용하여 객체를 생성하고 소멸자를 호출하게 되면

아래와 같이 자식 클래스의 소멸자가 호출이 되지 않는 상황이 발생한다.

 

이는 해당 객체가 사용한 메모리가 해제되지 않아 메모리 누수로 이어지게 된다.

 

따라서 동적 할당한 메모리는 반드시 해제해야 하며 이는 소멸자에 virtual을 사용함으로써

 

실제 객체의 타입이 자식 클래스임을 확인하고 정상적으로 메모리를 해제할 수 있도록 한다.

 

 

 

 


 

 

이처럼 가상함수를 사용하면 다형성을 구현할 수 있어 코드의 유연성과 확장성을 높일 수 있다.

 

하지만 가상함수의 호출은 일반 함수보다 느리기 때문에, 성능에 민감한 부분이라면 가상함수의 사용을 지양하는 것이 좋다.

 

 

 

 

 

 

'Develop > Cpp' 카테고리의 다른 글

[C++] STL cmath, map  (0) 2022.06.29
[C++] sort (정렬)  (0) 2022.06.25
[C++] STL vector  (0) 2022.06.23
[C++] String 클래스  (0) 2022.06.22
[Cpp] Direct 배열  (0) 2022.06.22