class Animal {
public :
virtual void breathe();
};
class Fish : public Animal {
public :
virtual void breathe();
}; |
■ 가상 함수의 호출
- 기반클래스의 포인터가 파생클래스의 객체를 가리킬 때, 그 포인터를 통해 가상함수를 호출하면 파생 클래스에서 재정의한 함수가 호출됨.
Animal* a = new Fish;
a -> breathe(); // Fish::breathe 호출 |
→ a->breathe(); 는 Fish::breathe 를 호출함. 즉, a->breathe(); 는 함수명으로는 Animal::breathe를 호출하지만, 내용으로는 Fish::breathe 가 호출되는 것임.
■ 기반클래스의 멤버함수가 virtual 을 갖고 있을 때
- 파생클래스에서 재정의한 함수는 virtual 을 생략해도 저절로 가상함수가 됨.
- 그러나 기반클래스의 가상 함수를 파생클래스에서 재정의할 때도 virtual 을 명시해주는 것이 관례임. (소스코드 이해가 수월하기 위해서...)
2. 가상함수의 특징
■ 동적바인딩(Dynamic Binding)
- 가상함수는 동적결합(Dynamic Binding)을 함. (코드만 봐서는 어떤 코드가 실행될지 정해지지 않은 상태. 즉, 객체를 생성하는 시점에 생성조건에 따라 실행코드가 결정된다.)
- 일반함수는 정적 결합을 하는 반면, 가상 함수는 동적 결합을 함.

■ 가상함수(동적 바인딩)의 장단점
- 장점 : 실행시 다형성(Runtime Polymorphism) 이라는 융통성을 갖게 됨. (상황에 생성하는 조건에 따라 실행 코드를 변경할 수 있다.)
- 단점 : 가상함수를 사용하면 실행속도에서는 손해

3. 가상 함수 테이블 (Virtual Function Table)
■ vtable (Virtual Function Table) 이란?
- 가상 함수에 대한 포인터 배열
- 가상 함수를 사용하는 클래스의 각 객체는 vtable을 가리키는 vptr이라는 숨겨진 포인터를 갖고 있음.
- 가상함수를 포함한 클래스의 객체를 생성할 때 vtable이 만들어짐.
- 객체에서 가상함수를 호출하면 vptr를 통해 vtable에 있는 해당함수를 찾아 수행시킴.
■ 파생클래스에서의 vtable
- 기반클래스의 객체로부터 vtable을 물려 받아 수정 및 확장함.
- 파생클래스에서 재정의한 가상함수가 있다면 기반클래스의 가상 함수에 대한 포인터가 들어 있던 곳을 파생클래스에서 재정의한 가상함수에 대한 포인터로 변경함.
- 기반클래스의 포인터가 파생클래스의 오브젝트를 가리킬 때 그 포인터를 통해 가상 함수를 호출하면 파생클래스에서 수정한 vtable 을 사용하게 되므로 파생 클래스에서 재정의한 함수가 호출됨.
4. 가상 소멸자
■ 상속 관계에서 기반클래스의 포인터가 파생클래스의 동적 객체를 가리킬 때 그 포인터를 통해 동적 객체를 제거하면...?
기본적으로 기반 클래스의 소멸자만 호출되고 파생 클래스의 소멸자는 호출되지 않음.
class Animal {
};
class Fish : public Animal {
};
Animal* a = new Fish;
delete a; |
■ 기반클래스의 포인터가 파생클래스의 동적 객체를 가리키고, 그 포인터를 통해 동적 객체를 제거했을 때 파생클래스의 소멸자까지 호출되도록 하려면...?
기반 클래스의 소멸자를 다음과 같이 가상 소멸자(Virtual Destructor)로 만들어야 함.
class Animal {
public :
Animal();
virtual ~Animal();
};
class Fish : public Animal {
public :
Fish();
virtual ~Fish();
};
Animal* a = new Fish;
delete a; |
→ delete a;는 Animal::~Animal 뿐만 아니라 Fish::~Fish 도 호출함.
[ 순수가상함수(Pure Virtual Function) ]
1. 순수 가상 함수
■ 순수 가상 함수 (Pure Virtual Function) 란?
- 선언만 있고 정의가 없는 가상 함수
- 파생 클래스에서 재정의할 것으로 예상되는 함숭에 대해 미리 호출 계획을 세워 두기 위해 정의.
■ 가상함수를 순수 가상함수로 만드는 방법
- 다음의 예와 같이 선언시 0을 지정하면 됨.
- 여기서 '=0'은 Pure Specifier임.
class Animal {
public :
virtual void breathe() = 0;
};
class Fish : public Animal {
public :
Fish();
virtual void breathe();
}; |
→ Animal::breathe 는 순수 가상함수임. Animal::breathe 는 함수명만 제공하고, 내용은 Fish::breathe 에서 제공하게 됨.
2. 추상클래스 (Abstract Class)
■ 추상클래스란?
- 순수 가상함수를 포함하고 있는 클래스.
- 파생클래스의 행동양식을 미리 설계하기 위한 기반클래스임.
- 일반적으로 순수 가상함수를 통해 인터페이스를 제공하고 파생클래스에서 그 인터페이스를 구현하게 됨.
class Animal { // 추상클래스 Animal
public :
virtual void breathe() = 0;
}; |
■ 추상클래스의 특징
- 추상클래스의 객체를 생성하는 것은 불가능. ∵ 정의되지 않은 함수를 가지고 있기 때문...
- 추상클래스는 파생클래스를 정의하여 순수가상함수를 구현하였을 때 객체를 생성할 수 있다.
- 추상클래스의 포인터를 선언하는 것은 가능함.
- 추상클래스의 포인터는 흔히 파생 클래스의 객체를 가리키게 되는데, 추상클래스의 포인터가 파생클래스의 객체를 가리킬 때 그 포인터를 통해 가상함수를 호출하면 파생클래스에서 재정의한 함수가 호출됨.
Animal* a = new Fish;
a -> breathe(); // 추상클래스 Animal
delete a; |
2. 순수 가상함수의 재정의
■ 순수 가상함수의 재정의
- 순수 가상함수는 사용하기 전에 반드시 재정의해야 함.
- 만일 기반 클래스가 순수 가상 함수를 포함하고 있는데 파생 클래스에서 그것을 재정의하지 않는다면 그 파생 클래스도 추상 클래스가 됨.
→ 기반클래스 뿐만 아니라, 그 파생클래스의 객체를 생성하는 것도 불가능 함.
class Animal { // 추상클래스 Animal
public :
virtual void breathe() = 0;
};
class Fish : public Animal {
public :
void swim();
};
Animal* a = new Animal; // error
Animal* f = new Fish; // error |