가상함수
가상함수 만들기
멤버함수의 맨 앞에 virtual을 붙이면 그 멤버함수는 가상함수가 됨.
가상함수는 자식 클래스에서 반드시 구현을 해줘야 함. (안 그러면 컴파일이 안됨)
cpp
#include <iostream>
using namespace std;
class Base {
virtual void show() { cout << "Base::Show" << std::endl; }
};
class Derived : public Base {
void show() override { cout << "Derived::show" << endl;}
};
int main() {
Base* basePtr = new Derived();
basePtr->show(); // Derived 클래스의 show 함수가 호출됨
delete basePtr;
return 0;
}
순수 가상함수
아무것도 적히지 않은(=구현부가 없는) 가상함수
-
부모 클래스에서는 구현부가 없고, 자식 클래스에 구현부가 존재.
-
cpp에 존재하지 않는 인터페이스를 순수 가상함수로 구현할 수 있음.
(클래스 전체를 순수 가상함수로 채우면 됨.)
cpp
Class Base
{
public :
virtual void somefun() = 0;
};
가상함수의 특징
런타임 다형성
-
가상 함수는 런타임 시에 객체의 실제 타입을 기반으로 호출
이는 포인터나 참조를 통해 객체에 접근할 때 유용하게 사용.
-
예를 들어, 부모 클래스의 포인터를 통해 자식 클래스의 가상 함수를 호출할 수 있음.
cpp
using namespace std;
class Base {
virtual void foo() { cout << "Base::foo()" << endl;}
};
class Derived : public Base {
void foo() override { cout << "Derived::foo()" << endl; }
};
int main() {
Base* obj = new Derived();
obj->foo(); // Derived::foo() 가 호출됨
delete obj;
return 0;
}
동적 바인딩
- 함수가 어떤 코드를 통해 실행될 지를 프로그램이 실행될 때 결정하는 것.
- 일반적인 함수는 정적 바인딩이 이뤄진다. 함수가 어떤 코드를 통해 실행될 지를 프로그램이 개발될 때 결정하게 된다는 뜻.
- 이 과정에서 가상함수 테이블(vTable)을 사용
가상함수 테이블
- 영어로는 virtual function table, 줄여서 vTable 이라고 함.
- 가상함수를 하나라도 가진 클래스라면, 해당 클래스가 객체화될 때마다 무조건 하나의 가상함수 테이블이 생성됨.
cpp
부모 클래스 AAA
key value
------------------------------
void AAA::func1() --> 0x1024
void AAA::func2() --> 0x2048
자식 클래스 BBB
key value
------------------------------
void BBB::func1() --> 0x3084
void AAA::func2() --> 0x2048
void BBB::func1() --> 0x4096
- BBB클래스의 가상함수 테이블을 살펴보면, 오버라이딩 된 AAA클래스의 func1 함수에 대한 정보가 없다.
- 그래서 BBB클래스의 Func1함수가 대신 호출되는데, 이것이 바로 가상함수의 호출원리이다.
cpp
< 가상 소멸자 >
* 기반클래스의 포인터가 파생클래스의 동적 객체를 가리키고,
그 포인터를 통해 동적 객체를 제거했을 때 파생클래스의 소멸자까지
호출되도록 하려면…?
기반 클래스의 소멸자를 다음과 같이
가상 소멸자(Virtual Destructor)로 만들어야 함.
가상함수의 장/단점 정리
cpp
<장점>
- 실행 시 다형성이라는 융통성을 갖게 됨
- 생성조건 및 상황에 따라 코드 변경.
<단점>
- 가상함수 테이블 때문에 일반적인 클래스보다 공간을 좀 더 차지
- 가상함수 테이블 때문에 조금 더 호출 시간이 길어진다.