가상 함수는 크게 순수가상함수와 단순가상함수로 구분할 수 있다.
순수가상함수는 다음과 같이 선언한다.
순수가상함수는 다음과 같이 선언한다.
virtual void Display() = 0;
단순가상함수는 다음과 같이 선언한다.
virtual void Display();
그렇다면, 순수가상함수와 단순가상함수의 차이점을 알아보자.
1. 클래스가 순수가상함수를 가지게 되면, 추상클래스가 되기때문에 인스턴스를 직접 생성할 수 없다.
2. 순수가상함수는 자식클래스에서 무조건 재정의해야하지만, 단순가상함수는 재정의하지 않아도 되며, 이 경우 자식클래스는 부모클래스의 단순가상함수를 사용하게 된다.
그렇다면 위와 같은 차이점이 어떠한 형태로 활용되는지 알아보자.
1. 자식클래스에게 인터페이스(정의부가 없는 함수의 형태)만 계승하고자 할 경우에는 순수가상함수로 선언을 한다. -> 이 경우는 자식클래스가 특정 함수를 꼭 가지고 있어야 하지만 그 정의부가 일정하지 않기때문에 스스로 정의해야할 경우에 사용한다.
2. 자식클래스에게 인터페이스를 계승하거나 기본구현(디폴트 함수)을 계승하도록 하여 자식클래스 스스로 선택할 수 있게하려면 단순가상함수로 선언을 한다. -> 이 경우는 기본적으로는 부모클래스의 함수를 이용하면 되지만 간혹 해당 함수를 재정의하여 사용할 필요가 있을 경우에 사용한다.
마지막으로 가상함수를 이용하여 다형성을 구현하는 방법에 대해서 알아보도록 하자.
다형성이란, 부모클래스 하나로 다양한 형태의 자식클래스들을 활용(함수호출)할 수 있도록 만드는 것이다.
기본적으로 부모클래스와 같은 이름의 함수를 자식클래스에서 재정의한다고 하더라도 자식객체의 레퍼런스를 가지고 있는 부모객체에서 해당함수를 호출하면 자식객체의 함수가 호출되지 않고 자기자신의 함수가 호출이 된다. 하지만 부모객체의 함수를 선언할 때 가상함수로 선언을 하게 되면 부모객체의 함수가 아닌 자식객체의 함수가 호출이 된다. 이를 이용하여 다형성을 구현하게 되는 것이다.
이 역시 말보다는 코드를 보는게 이해에 도움이 될 것이다.
다음 코드의 예제를 보자.
class Snake { public: virtual void Attack() { cout << "Snake Attack" << endl; } }; class Anaconda:public Snake { public: virtual void Attack() { cout << "Anaconda Attack" << endl; } }; class Cobra:public Snake { public: virtual void Attack() { cout << "Cobra Attack" << endl; } }; void SnakeAttack(Snake& snake) { snake.Attack(); } int _tmain(int argc, _TCHAR* argv[]) { Cobra africaCobra; Anaconda amazonAnaconda; SnakeAttack(africaCobra); SnakeAttack(amazonAnaconda); return 0; }
예제코드에서 SnakeAttack()라는 함수는 Snake객체를 인자로 받아 그 멤버함수를 호출하고 있다. 하지만 실제로 넘겨주는 객체는 Anaconda와 Cobra객체이고 이렇게 넘겨주는 객체에 따라 다른 결과가 나오도록 하는게 바로 virtual 키워드의 핵심이다.
그렇기 때문에 결과값은 다음과 같다.
Cobra Attack
Anaconda Attack
Anaconda Attack
하지만 만약에 virtual 키워드가 없다면 결과값은 다음과 같을 것이다.
Snake Attack
Snake Attack
Snake Attack
즉, 모두 부모 객체의 멤버함수만이 실행이 되어버린다.
이제 이 다형성을 구현할 경우에 주의할 점에 대해서 알아보자.
위와 같은 코드에서 Snake객체를 이용하여 Cobra객체와 Anaconda객체를 제거하려고 할 경우에는 문제가 생긴다. Snake코드의 소멸자를 실행시킨다고 Cobra객체와 Anaconda객체의 소멸자가 실행된다는 보장이 없기때문이다.
이를 해결하기 위해선, Snake객체의 소멸자 역시 가상함수로 선언을 하면 된다.
이렇게 소멸자를 가상함수로 선언하게되면, 부모객체가 소멸할 때 자식객체의 소멸자를 먼저 실행한 후 부모객체의 소멸자가 실행되어 부모객체를 이용하여 자식객체까지 안전하게 제거를 할 수 있게된다.
이상으로 가상(virtual) 함수의 의미와 활용법, 그리고 이를 활용한 클래스의 다형성에 대해서 알아보았다. 혹시 궁금하거나 잘못된 부분이 있을 경우, 댓글로 남겨주기 바란다.
이제 이 다형성을 구현할 경우에 주의할 점에 대해서 알아보자.
위와 같은 코드에서 Snake객체를 이용하여 Cobra객체와 Anaconda객체를 제거하려고 할 경우에는 문제가 생긴다. Snake코드의 소멸자를 실행시킨다고 Cobra객체와 Anaconda객체의 소멸자가 실행된다는 보장이 없기때문이다.
이를 해결하기 위해선, Snake객체의 소멸자 역시 가상함수로 선언을 하면 된다.
class Snake { public: virtual void Attack() { cout << "Snake Attack" << endl; } Snake(); virtual ~Snake(); // 소멸자를 가상함수로 선언한다. };
이렇게 소멸자를 가상함수로 선언하게되면, 부모객체가 소멸할 때 자식객체의 소멸자를 먼저 실행한 후 부모객체의 소멸자가 실행되어 부모객체를 이용하여 자식객체까지 안전하게 제거를 할 수 있게된다.
이상으로 가상(virtual) 함수의 의미와 활용법, 그리고 이를 활용한 클래스의 다형성에 대해서 알아보았다. 혹시 궁금하거나 잘못된 부분이 있을 경우, 댓글로 남겨주기 바란다.