C++프로그래밍/이론 정리

vitrual키워드와 가상함수테이블

season97 2023. 11. 29. 13:11
728x90
반응형

 

virtual 키워드 동작 방식

 

ㆍ virtual 키워드는 다형성을 구현하기 위해 사용됨

 

※ 다형성이란 하나의 인터페이스가 여러 형태로 동작하는 객체지향 프로그래밍의 핵심 개념 중 하나

 

ㆍ virtual 키워드는 클래스의 맴버 함수를 가상함수로 선언하는데 사용되며 가상 함수는 base클래스에서 선언되고 파생 클래스에서 재정의 될 수 있다.

 

ㆍ 기본 클래스의 포인터나 참조를 통해 가상 함수를 호출하면 C++ 런타임은 실제 객체의 타입을 확인하고 해당 타입의 가상 함수를 호출함 -> 이를 "동적 바인딩" 또는 "런타임 다형성" 이라 함

 

ㆍ 이를 가능하게 해주는 것이 vtable 이라는 가상함수 테이블 덕분

 

ㆍ vtable은 각 클래스마다 존재하며 가상 함수의 주소를 저장하고 있다.

객체가 생성될 때 컴파일러는 vtable을 생성하고 각 가상 함수에 대한 포인터를 vtable에 추가한다. 그리고 객체의 메모리에는 vtable에 대한 포인터가 저장된다.

가상함수 테이블에 주소가 저장되는 예시

1. 가상 함수를 호출할 때 컴파일러는 객체의 vtable 포인터를 찾아감

2. vtable에서 해당 함수의 엔트리를 찾아 해당 함수를 실행

3. 이 과정을 통해 실제 객체의 타입에 따른 적절한 함수가 호출됨

 

가상함수 테이블을 사용한 간단한 예시

 

1. Animal 클래스의 sound()함수는 virtual 키워드로 선언되어 있기 때문에 Dog클래스에서 재정의 될 수 있다.

2.  main함수에서 Animal클래스의 포인터로 Dog클래스의 인스턴스를 카리키게 하고 Sound함수를 호출하면 Dog클래스의 Sound함수가 실행된다. 

3. 이를 가능하게 하는 것이 virtual 키워드와 가상함수테이블을 통한 동적 바인딩 덕분

 

※ 단 virtual 키워드를 사용하면 실행 시간에 추가적인 비용이 들 수 있다

-> vtable을 조회하고 적절한 함수를 찾는 과정이 필요하기 떄문 but 비용은 대체로 무시할 수 있는 수준이며 코드의 유연성을 크게 향상시킬 수 있다.

 

override 키워드 명시 이유

 

ㆍ 잠재적인 오류를 런타임이 아닌 컴파일 타임에 잡아낼 수 있기 때문

 

ㆍ 이 키워드를 명시하면 컴파일러가 이 함수가 기본 클래스의 가상함수를 정확히 재정의하고 있는지 체크한다.(일치하지 않을 시 컴파일 오류를 발생시킴)

이 키워드를 명시하지 않을 경우 컴파일러는 오류를 발생 시키지 않아 의도와는 다른 결과를 초래할 수 있다.

 

※ 공부를 하다 이해하기론 어떤 가상함수가 호출 될지는 런타임 시점에서 결정되는데 이는 컴파일러가 이미 컴파일을 완료 해서 알지 못하는 시점이기 때문에 오류를 발생시키지않고 프로그램을 동작 시켜 원치않는 결과를 얻을 수 있으니 이것을 미리 방지하기 위해 override키워드를 사용한다.

 

 

▶ 가상함수 테이블이란?

 

ㆍ 클래스에 정의된 가상 함수들의 주소를 저장하는 테이블 (함수 포인터의 배열)

-> 런타임 다형성을 지원하기 위한 중요한 매커니즘

 

ㆍ 각각의 클래스는 자신만의 가상 함수 테이블을 가지고 있다. 가상함수가 클래스 내에 선언되면 해당 함수의 주소는 그 클래스의 가상함수테이블에 추가된다.

 

ㆍ 클래스의 객체가 생성될 때 이 객체는 가상 함수 테이블에 대한 포인터를 가진다

 


 

 

▶ new와 malloc의 개념과 차이점 가상함수 테이블과의 연관 관계

 

ㆍ 메모리를 동적으로 할당하는데 사용되는 키워드

 

차이점

 

1. new 는 C++에 도입된 연산자, malloc은 C언어에서 부터 사용되던 함수

 

2. new는 메모리를 할당하는 동시에 객체의 생성자를 호출. 따라서 초기화가 필요한 타입의 경우 new를 사용하는것이 좋다. 반면 malloc은 단순히 메모리를 할당만 하며 초기화나 생성자 호출은 수행하지 않는다.

 

3. new는 할당에 실패하면 예외를 발생시킨다 malloc은 할당에 실패하면 NULL을 반환한다.

 

4. new로 할당된 메모리는 delete를 사용해 해제해야 하고, malloc으로 할당된 메모리는 free를 사용해 해제해야 한다.

 

5. new는 타입에 따라 적절한 메모리 크기를 자동으로 계산해 주지만, malloc은 할당하려는 메모리의 크기를 명시적으로 지정해야 한다.

 

※ new를 통해 생성된 클래스의 객체가 가상함수 테이블을 가지게 된다. malloc의 경우 메모리를 할당하는 역할만 수행하며 객체의 생성자를 호출하거나 가상함수테이블을 설정하는 등의 작업은 수행하지 않음.

 

 

▶ 가상함수테이블을 생성할 때 메모리 상에서 일어나는 과정

 

ㆍ 가상 함수 테이블은 컴파일러에 의해 자동으로 생성되며, 가상 함수를 가지는 클래스에 대해 각 클래스 마다 하나씩 생성되며 이 과정은 컴파일 타임에 발생한다. 

 

--- 정적 바인딩--- 컴파일 타임

1. 가상 함수 테이블 생성 : 컴파일러는 가상 함수를 가지는 각 클래스에 대해 가상 함수 테이블을 생성한다. 이 테이블은 프로그램의 데이터 영역에 저장된다.

각 가상 함수 테이블은 해당 클래스의 가상 함수들을 나타내는 함수 포인터의 배열이다. 함수 포인터들은 가상 함수들이 선언된 순서대로 배열에 저장된다.

 

2. 객체 생성 시 가상 함수 테이블 포인터 설정: new연산자를 사용해 클래스의 객체를 생성할 때, 컴파일러는 객체의 메모리 레이아웃의 처음 부분에 가상 함수 테이블에 대한 포인터를 저장한다. 이 포인터는 해당 객체 클래스의 가상 함수 테이블을 가리킨다.

 

--- 동적 바인딩--- 런타임

3. 가상 함수 호출 : 가상 함수를 호출할 때 컴파일러는 먼저 객체의 가상 함수 테이블 포인터를 사용 해 가상 함수 테이블에 접근한다. 그리고 테이블에서 해당 가상 함수에 대한 함수 포인터를 찾아 해당 함수를 호출한다.

 

 

 

 

 

 

 

728x90
반응형

'C++프로그래밍 > 이론 정리' 카테고리의 다른 글

unordered_map과 해시  (0) 2023.12.07
상수에 대해 (const)  (0) 2023.12.06
형변환 연산자  (0) 2023.12.04
STL  (0) 2023.10.13
쓰레드, 코루틴, 델리게이트와 스마트포인터  (0) 2023.10.12