가상 함수란
파생된 클래스에서 재정의할 것으로 기대하는 멤버 함수를 뜻한다.
자신을 호출하는 객체의 동적 타입에 따라 호출할 함수가 결정된다.
virtual 반환형식 함수명 // 문법
virtual void print(); // 예시
재정의하지 않았을 때 발생하는 이슈
함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환된다. (정적 바인딩 또는 초기 바인딩이라고 함)
일반 멤버 함수는 정적 바인딩을 하게 되는데 이때 일반 멤버 함수를 오버로딩해서 사용할 경우 문제가 발생한다.
#include <iostream>
using namespace std;
class Parent {
public:
void print() { cout << "parent class" << endl; }
}
class Child : public Parent {
public:
void print() { cout << "child class" << endl; }
}
int main()
{
Parent* ptr;
Parent p;
Child c;
ptr = &p;
ptr->print();
ptr = &c;
ptr->print();
}
실행 결과
parent class
parent class
Parent 타입의 포인터 p로 Parent 클래스의 print() 함수를 호출하고 포인터 p에 Child 객체의 주소를 넣어줬음에도 불구하고 Parent 클래스의 print() 함수가 호출됐다. 이는 컴파일 타임에 이미 호출될 함수의 주소가 결정났기 때문에 Parent 클래스의 함수가 호출된 것이다.
이를 해결하기 위해서는 일반 멤버 함수를 가상 함수로 만들어 동적 바인딩을 해야 한다.
#include <iostream>
using namespace std;
class Parent {
public:
virtual void print() { cout << "parent class" << endl; }
}
class Child : public Parent {
public:
virtual void print() { cout << "child class" << endl; }
}
int main()
{
Parent* ptr;
Parent p;
Child c;
ptr = &p;
ptr->print();
ptr = &c;
ptr->print();
}
실행 결과
parent class
child class
소멸자에 virtual 키워드를 붙여야 되는 이유
앞서 말했던 재정의하지 않았을 때 발생하는 이슈와 같다.
소멸자를 가상으로 선언하지 않고 자식 클래스에서 이를 재정의하지 않는다면 자식 클래스의 소멸자가 호출되지 않아 리소스가 정상적으로 해제되지 않으면서 메모리 누수로 연결될 것이다.
그렇기 때문에 기초 클래스의 소멸자는 반드시 가상으로 선언되어야 한다.
가상 함수 테이블
컴파일러는 가상 함수를 한 개라도 가지는 클래스에 대해 가상 함수 테이블을 작성한다.
작성된 가상 함수 테이블에 선언된 가상 함수들의 주소를 저장하게 된다.
가상 함수를 호출할 때 가상 함수 테이블에 접근하여 함수의 주소를 찾아 호출한다.
'c++' 카테고리의 다른 글
[C++] 스마트 포인터 - unique_ptr, shared_ptr, weak_ptr (0) | 2023.06.09 |
---|---|
[C++] 포인터와 참조자 - Pointer, Reference (0) | 2023.06.08 |
[C++] 객체 지향 프로그래밍(OOP, Object Oriented Programming) (0) | 2023.06.08 |
[C++] Call by value, Call by reference (0) | 2023.06.07 |
[C++] 객체 생성 시 초기화 (0) | 2023.06.07 |
댓글