가상함수
1. 가상함수 기본 개념
- 집고 넘어가는 다형성 : 자식은 부모의 역할을 할 수 있는데, 부모는 자식의 역할을 할 수 없다.
- 포인터 타입의 양면성 : 포인터가 가르치는 객체의 타입(동적 타입), 포인터가 선언될 당시의 타입(정적 타입)
int* a;
char b = 'c';
a = &b;
//a의 정적 타입은 int, a의 동적 타입은 char
- 객체 포인터에다 '기본' 함수를 호출하면 정적 타입에 명시된 함수가 호출된다. (정적 바인딩)
- 객체 포인터에다 '가상' 함수를 호출하면 동적 타입에 명시된 함수가 호출된다. (동적 바인딩)
- 동적 바인딩을 구현하는 방식은 컴파일러마다 다르지만 일반적으로 vTable으로 구현한다.
- virtual함수를 생성하면 그 클래스 이하 모든 자식 클래스에게 vPtr을 멤버로 (몰래)생성하고 vTable에 가상함수와 함께 등록한다.
- 가상함수가 호출되면 호출한 객체(동적 타입)에서 vPtr을 찾아서 vTable의 일치하는 함수를 호출한다.
- 따라서 포인터가 가르치는 객체(동적 타입)의 함수를 호출할 수 있게 하는 것.
- 동적 바인딩은 vTable에서 가상함수를 찾는 과정을 거치므로 성능 저하를 야기한다.
//예제 코드
class Parent
{
public:
void funcStatic(){ printf("부모\n");} //일반함수
virtual void funcDynamic(){printf("부모\n");} //가상 함수
}
class Child : public Parent
{
public:
void funcStatic(){ printf("자식\n");}
virtual void funcDynamic(){printf("자식\n");}
}
Parent* node;
node = Child();
node->funcStatic(); // 정적 바인딩으로 호출되므로 '부모' 출력
node->funcDynamic(); // 동적 바인딩으로 호출되므로 '자식' 출력
int* a;
char b = 'c';
a = &b;
//a의 정적 타입은 int, a의 동적 타입은 char
- virtual함수를 생성하면 그 클래스 이하 모든 자식 클래스에게 vPtr을 멤버로 (몰래)생성하고 vTable에 가상함수와 함께 등록한다.
- 가상함수가 호출되면 호출한 객체(동적 타입)에서 vPtr을 찾아서 vTable의 일치하는 함수를 호출한다.
- 따라서 포인터가 가르치는 객체(동적 타입)의 함수를 호출할 수 있게 하는 것.
//예제 코드
class Parent
{
public:
void funcStatic(){ printf("부모\n");} //일반함수
virtual void funcDynamic(){printf("부모\n");} //가상 함수
}
class Child : public Parent
{
public:
void funcStatic(){ printf("자식\n");}
virtual void funcDynamic(){printf("자식\n");}
}
Parent* node;
node = Child();
node->funcStatic(); // 정적 바인딩으로 호출되므로 '부모' 출력
node->funcDynamic(); // 동적 바인딩으로 호출되므로 '자식' 출력
2. 가상함수 활용
- 가상함수의 동적 바인딩 덕분에 다형성을 잘 살릴 수 있음.
void main()
{
//부모 클래스의 컨테이너에 다양한 자식들을 몰아넣고
Graphic *ar[10]={
new Graphic(),new Rect(),new Circle(),new Rect(),new Line(),
new Line(),new Rect(),new Line(),new Graphic(),new Circle() };
int i;
//한꺼번에 드로! 동적 바인딩으로 각 자식 객체에 알맞은 draw가 호출된다.
for (i=0;i<10;i++) {
ar[i]->Draw();
}
for (i=0;i<10;i++) {
delete ar[i];
}
}
- 라이브러리의 클래스(ex cocos2d-x의 Node)를 상속받는 나만의 클래스를 만들때, 기존의 함수를 재정의하는데 사용된다.
- 상속받는 클래스의 소멸자에 (필히) 사용된다.
- 자식 클래스의 소멸자를 가상함수로 만들어 주지 않는다면, 자식 클래스의 인스턴스가 부모클래스 타입의 포인터에 있다면, 소멸될때 부모 클래스의 소멸자가 호출된다.
- 이때 자식클래스가 부모 클래스와 다른 멤버 변수를 가지고 있고, 그것이 메모리를 동적 할당받아서 만들어 낸 것이라면, 이 멤버 변수가 차지하는 메모리 영역을 해제하는 소멸자가 호출되지 않는다.
- 따라서 상속을 받는 모든 클래스는 소멸자를 가상함수로 만들어주는 것이 좋다.
void main()
{
//부모 클래스의 컨테이너에 다양한 자식들을 몰아넣고
Graphic *ar[10]={
new Graphic(),new Rect(),new Circle(),new Rect(),new Line(),
new Line(),new Rect(),new Line(),new Graphic(),new Circle() };
int i;
//한꺼번에 드로! 동적 바인딩으로 각 자식 객체에 알맞은 draw가 호출된다.
for (i=0;i<10;i++) {
ar[i]->Draw();
}
for (i=0;i<10;i++) {
delete ar[i];
}
}
- 자식 클래스의 소멸자를 가상함수로 만들어 주지 않는다면, 자식 클래스의 인스턴스가 부모클래스 타입의 포인터에 있다면, 소멸될때 부모 클래스의 소멸자가 호출된다.
- 이때 자식클래스가 부모 클래스와 다른 멤버 변수를 가지고 있고, 그것이 메모리를 동적 할당받아서 만들어 낸 것이라면, 이 멤버 변수가 차지하는 메모리 영역을 해제하는 소멸자가 호출되지 않는다.
- 따라서 상속을 받는 모든 클래스는 소멸자를 가상함수로 만들어주는 것이 좋다.
3. 순수 가상함수
- 자식 클래스가 반드시 재정의를 해야하는 함수
//기본 형식
class Graphic
{
public:
//이런걸 순수 가상 함수라고 부른다.
virtual void Draw()=0;
};
- 순수 가상함수가 있는 클래스(추상 클래스)는 인스턴스 생성이 불가능하다. 정의되지 않은 함수가 있기 때문!
- 추상 클래스는 파생되는 자식 클래스들의 기본적인 형태를 구체화하고, 그 객체들의 집합을 관리하는데 사용된다.
//기본 형식
class Graphic
{
public:
//이런걸 순수 가상 함수라고 부른다.
virtual void Draw()=0;
};
'컴퓨터 > C++' 카테고리의 다른 글
c++ 함수포인터 부터 std::function까지 (0) | 2015.03.10 |
---|---|
FSM과 State 패턴 (0) | 2015.03.10 |
c++의 여러가지 생성자들 (0) | 2015.03.10 |
c++의 static 변수에 대하여 (0) | 2015.03.10 |