가상함수란?
- 가상함수는 객체지향 프로그래밍에서 상속과 다형성을 구현하기 위해 사용되는 개념 중 하나이다.
C++의 가상 함수 선언
- C++ 에서 가상함수를 선언하기 위해선 가상함수를 선언할 부모 클래스와 이를 상속 받을 자식 클래스를 정의한다.
- 부모 클래스에서 자식 클래스에게 재정의하게 할 함수에 'virtual' 키워드를 붙인다.
- 자식 클래스에선 재정의할 수 있는 선택지가 주어지며 재정의할 경우 함수 이름 뒤에 'override' 키워드를 붙일 수 있다.
#include <stdio.h>
#include <string>
using namespace std;
class Character // 추상 클래스 => 순수가상함수가 하나라도 있으면 된다 .
{
public:
Character(int id, string name)
{
this->id = id;
this->name = name;
printf("캐릭터 생성 %p - %s \n", this, this->name.c_str());
}
virtual ~Character() // 가상 소멸자
{
printf("캐릭터 소멸 %p - %s \n", this, this->name.c_str());
}
virtual void Move()
{
printf("캐릭터 이동 %p - %s \n", this, this->name.c_str());
}
virtual void Attack() = 0; // 순수 가상함수
protected:
int id;
string name;
};
class Player : public Character
{
public:
Player(int id, string name)
: Character(id, name)
{
printf("플레이어 생성 %p - %s \n", this, this->name.c_str());
}
~Player() override
{
printf("플레이어 소멸 %p - %s \n", this, this->name.c_str());
}
// C++은 주소가 다르기 때문에 부모랑 이름이 같아도 된다 (가상화 전)
void Move() override
{
Character::Move();
printf("플레이어 이동 %p - %s \n", this, this->name.c_str());
}
void Attack() override
{
printf("플레이어 공격 %p - %s \n", this, this->name.c_str());
}
};
int main()
{
//Character character(1, "Character");
Player player(2, "Player");
//player.Move();
// 가상 함수가 있으니 정적 생성 불가
//Character player2 = player; // 업캐스팅
//player2.Move(); //
// 가상이 이뤄지면 소멸자도 가상화해야함.
Character* player3 = new Player(3, "Player3");
player3->Move();
player3->Attack();
delete player3;
return 0;
}
추상클래스와 순수 가상함수
- 추상화의 개념을 위해 추상 클래스를 선언할 수 있다. C++에선 이를 위해 "순수 가상함수"를 선언을 하기만 하면 해당 클래스는 추상 클래스로 취급한다.
순수 가상함수
- 순수 가상함수는 'virtual' 키워드로 기존 가상함수처럼 선언하되 함수 몸체를 정의하지 않고 ' = 0' 을 할당하는 모습을 취하여 보인다.
- 순수 가상함수는 반드시 정의해야 한다. 해당 클래스를 상속받는 객체는 순수 가상함수를 오버라이딩해야 하며 추상 클래스는 정적할당을 할 수 없다.
virtual void Attack() = 0; // 순수 가상함수
가상 소멸자
- 가상 소멸자는 부모 클래스의 소멸자를 가상화한 소멸자를 말한다.
- 소멸자에 가상화를 미선언할 경우, 다형성을 위해 부모 클래스로 자식 클래스를 생성했을 때, 자식 클래스의 소멸자는 호출하지 않는 문제점이 발생하며 이는 메모리 누수를 일으킨다.
정적 결합
- 컴파일 하는 동안에 일어나는 결합
동적 결합
- 가상 함수를 사용하면 컴파일하는 동안 객체가 어떤 종류인지 모르기 때문에 (프로그램을 실행할 때 사용자가 결정하기 때문) 어떤 함수를 사용할 것인지 판단할 수 없으므로 프로그램을 실행할 때 가상 메소드가 선택되도록 하는 코드를 만드는 결합
결합
- 컴파일러가 함수 호출을 특정 블록에 있는 함수로 실행한다는 뜻.
정적결합보다 동적 결합이 더 좋은 이유
- 특별한 클래스형에 맞게 설계된 함수를 프로그램이 선택하도록 허용하기 때문이다.
정적 결합이 필요한 이요는 효율성과 개념 모델 때문이다.
C++은 기본적으로 정적 결합이 디폴트로 설정되어 있다. 이는 기초 클래스 포인터나 참조가 지시하는 객체의 종류가 무엇인지 추적하는 방법이 필요하기 때문이다. 굳이 상속하지 않은 클래스를 설계한다면 동적 결합이 필요 없기 때문에 처리 부담을 덜기 위함이 있다.
그리고 가상 함수로 재정의되면 안되는 함수가 존재할 수 있기 때문이다.
가상 테이블 (vtable)
가상 테이블은 가상 함수 포인터들을 모아 테이블 형식으로 구성된 것을 말한다.
가상 함수 포인터(vfptr)는 가상함수를 호출할 때 사용되는 포인터이다.
가상 함수 테이블 선언 위치
- 정적 할당 클래스에선 부모 클래스 바로 아래에서 구성된다.
- 동적 할당 클래스에선 자식 클래스와 부모 클래스 아래에서 구성되어진다.
'개발 > C++' 카테고리의 다른 글
가상 기초 클래스( virtual based class) (0) | 2024.06.28 |
---|---|
이름 공간(namespace) (0) | 2024.06.15 |
인라인(Inline) 함수 (2) | 2024.06.11 |
동적 캐스팅(dynamic_cast) (0) | 2024.06.07 |
매크로(Macro) (0) | 2024.06.07 |