30 - 2 . 가상 함수 의 활용 
 30 - 2 - 가 . 객체 의 집합 관리 
 가상 함수 의 정의 와 동작 그리고 내부 적 인 구현 방법 까지 알 아 봤 는데 이런 가상 함수 를 어떻게 잘 활용 할 수 있 을지 연구 해 보 자 . 가상 함수 를 꼭 사용 해야 하 는 경우 와 그렇 지 않 은 경우 가 있 는데 결론 만 얘기 하 자면 동적 결합 이 필요 할 때 가상 함수 를 사용 하 고 그렇 지 않 을 경우 는 비 가상 함수 를 사용 하 면 된다 . 그러나 이런 지침 만 으로 가상 함수 를 사용 해야 할 시점 을 정확 하 게 선정 하 기 란 쉽 지 않 으므로 가상 함수 를 제대로 활용 하 는 몇 가지 예 들 을 구경 해 보 도록 하 자 . 
 예 를 구경 해 보 면 가상 함수 의 정의 와 필요 성 , 그리고 장점 에 대해 확실 하 게 느낄 수 있 다 . 다음 예제 는 여러 가지 도형 을 그리고 관리 하 는 그래픽 편집 프로그램 의 구현 예 이 다 . 각각 의 그래픽 객체 들 은 Graphic 클래스 로부터 파생 되 는 클래스 의 객체 로 표현 하 며 모두 Draw 라는 멤버 함수 를 가지 고 있 어 스스로 자신 을 그릴 수 있 다 . 물론 콘솔 환경 에서 진짜 그래픽 을 그릴 수 는 없 으므로 문자열 을 출력 하 는 것 으로 그래픽 출력 흉내 만 낸다 . 
 예 제 : GraphicObject 
 class Line : public Graphic 
 class Circle : public Graphic 
 class Rect : public Graphic 
 new Graphic ( ) , new Rect ( ) , new Circle ( ) , new Rect ( ) , new Line ( ) , 
 4 개 의 클래스 가 정의 되 어 있 는데 그래픽 클래스 의 계층 은 다음 과 같 다 . 
 사용자 는 마우스 를 사용 하 여 그래픽 객체 들 을 그리고 이동 시키 고 편집 할 것 이 며 프로그램 은 사용자 에 의해 생성 되 는 그래픽 객체 의 집합 을 관리 하 기 위해 동적 배열 이나 연결 리스트 를 사용 해야 한다 . 이 예제 는 객체 의 집합 을 관리 하 기 위해 크기 10 의 Graphic * 형 배열 을 선언 하 고 이 배열 에 Graphic 파생 클래스 의 객체 포인터 를 저장 했 다 . 부모 형 의 포인터 가 자식 객체 를 가리킬 수 있 으므로 최상위 클 래스 인 Graphic 의 포인터 배열 을 선언 하 면 모든 그래픽 객체 의 집합 을 관리 할 수 있 다 . 
 이때 Graphic * 는 모든 자식 클래스 를 대표 하 는 대표 타입 이 며 이 타입 의 배열 은 모든 자식 객체 들 의 번지 를 저장 할 수 있 다 . 실제 프로그램 이 라면 이 배열 의 크기 는 동적 으로 관리 될 것 이 고 배열 내 의 객체 들 을 편집 하 는 기능 을 제공 해야 할 것 이 다 . 예 제 의 ar 배열 초기 식 은 사용 자 가 이런 객체 들 을 만들 어 놓 은 상황 을 가정 하 기 위한 것 이 다 . 
 이렇게 만들 어 진 객체 의 집합 을 화면 으로 출력 하 고자 한다면 루프 를 돌 며 배열 에 저장 된 객체 의 포인터 를 꺼내 각 객체 의 Draw 멤버 함수 를 호출 하 면 된다 . 모든 객체 들 은 스스로 를 그릴 수 있 는 Draw 멤버 함수 를 가지 고 있 다 . 그러나 실행 해 보 면 원 하 는 결과 는 나오 지 않 을 것 이 며 " 그래픽 오브젝트 입니다 " 만 10 번 출력 된다 . 
 왜 이렇게 출력 되 는가 하 면 ar 배열 이 Graphic * 타입 을 요소 로 가지 므로 ar [ i ] 에 의해 호출 되 는 Draw 는 항상 Graphic : : Draw 로 정적 결합 되 기 때문 이 다 . 이 문제 를 해결 하 려면 앞 에서 배운 대로 Draw 멤버 함수 를 가상 함수 로 선언 하 면 된다 . Graphic : : Draw 앞 에 만 virtual 을 붙이 면 파생 클래스 도 자동 으로 가상 이 된다 . 물론 원칙 대로 하 자면 모든 파 생 클래스 의 Draw 에 도 virtual 을 붙이 는 것 이 좋 다 . 
 이렇게 하 면 컴파일러 가 각 클래스 의 Draw 함수 번지 를 vtable 에 작성 하 고 생성 되 는 모든 객체 에 vptr 을 붙여 동적 결합 을 위한 준비 를 한다 . Draw 함수 는 자신 을 호출 하 는 객체 의 타입 에 맞 는 버전 으로 선택 ( 동적 결합 ) 될 것 이 고 배열 에 저장 된 객체 들 이 제대로 그려진다 . 
 그래픽 오브젝트 입니다 . 
 요건 사각형 입니다 . 
 동그라미 그렸 다 치 고 . 
 선 을 긋 습니다 . 
 똑같 은 ar [ i ] -> Draw ( ) 호출 임 에 도 ar [ i ] 가 가리키 는 동적 타입 에 따라 실제로 그려지 는 모양 은 달라지 는 데 그래서 가상 함수 의 동작 이 다형 적 이 라고 하 는 것 이 다 . 
 만약 동적 결합 을 하 는 가상 함수 라는 장치 가 없 다면 똑같 은 호출 로 다양 한 도형 을 그릴 수 가 없 다 . 각 객체 에 스스로 의 타입 을 판별 할 수 있 는 별도 의 열거 형 멤버 를 추가 하 고 이 멤버 로부터 타입 을 판별 하 여 자신 을 그릴 멤버 함수 를 결정 하 는 다중 분기 를 해야 할 것 이 다 . 
 case GR _ GRAPHIC : 
 case GR _ LINE : 
 case GR _ CIRCLE : 
 case GR _ RECT : 
 뿐 만 아니 라 이후 도형 의 종류 가 늘어나 면 이 분기 문의 case 도 같이 늘어나 야 하 므로 코드 를 관리 하 기 도 아주 어려워진다 . 이 에 비해 가상 함수 는 호출 객체 에 따라 선택 되 는 동적 결합 능력 이 있 으므로 ar [ i ] -> Draw ( ) 호출 만 하 면 Graphic 파생 클 래스 에 대해서 는 모두 정확 하 게 동작 할 뿐 만 아니 라 미래 에 새로운 클래스 가 추가 되 더라도 이 코드 는 더 이상 고칠 필요 가 없 어 진다 . 과연 그런지 삼각형 도형 을 추가 해 보 자 . 
 class Triangle : public Graphic 
 이 클래스 를 추가 하 고 main 의 ar 배열 에 삼각형 도형 생 성문 을 하나 작성 한 후 실행 해 보 면 삼각형 도형 도 잘 그려짐 을 확인 할 수 있 다 . 실제 도형 을 그리 는 코드 인 ar [ i ] -> Draw ( ) 는 그대로 사용 할 수 있 으며 전혀 편집 할 필요 가 없 다 . 심지어 이 코드 가 이미 컴파일 되 어 있 어도 확장 성 에 는 아무 문제 가 없 다 . vptr 로부터 vtable 을 찾 고 vtable 에서 호출 할 함수 를 찾 는 논리 는 동일 하 므로 참조 하 는 vtable 만 새로 추가 된 도형 의 것 으로 바뀌 면 된다 . 프로그램 을 확장 하 려면 클래스 는 계속 늘려야 겠지만 객체 들 을 관리 하 는 코드 는 더 이상 수정 하 지 않 아도 되 는 것 이 다 . 
 처음 부터 클래스 계층 을 조직 적 으로 설계 하 고 가상 함수 를 잘 작성 해 놓 으면 코드 관리 의 유연 성 이 극 적 으로 향상 된다 . 실제로 이런 그래픽 을 그리고 관리 하 는 대표 적 인 프로그램 인 파워 포인터 의 경우 를 보 자 . 이 프로그램 은 다양 한 각 약 각색 의 도형 들 을 그리고 관리 할 수 있 다 . 
 이 프로그램 의 내부 에 는 모든 도형 들 을 대표 할 수 있 는 클래스 타입 ( 예 를 들 면 Graphic 이나 Shape 등 ) 이 선언 되 어 있 을 것 이 고 각 도형 들 은 이 클래스 의 파생 클래스 로 표현 될 것 이 다 . 파생 클 래스 들 은 도형 관리 에 필요 한 모든 멤버 함수 를 도형 에 맞 는 가상 함수 로 정의 하 고 있 다 . 그래서 똑같 은 방법 으로 가상 함수 만 호출 하 면 모든 도형 을 일관 된 방법 으로 관리 할 수 있 는 것 이 다 . 그리 기 뿐 만 아니 라 도형 을 편집 하 는 코드 들 도 모두 마찬가지 이 다 . 
 마우스 드래그 시 : Move 가상 함수 호출 
 트래커 드래그 시 : Resize 가상 함수 호출 
 더블클릭 시 : SetProperty 가상 함수 호출 
 만약 이런 식 으로 가상 함수 를 사용 하 지 않 는다면 수많 은 도형 에 대해 또한 각 동작 에 대해 if else if 나 switch case 로 관리 해야 하 는데 이 는 너무 너무 비 효율 적 이 고 복잡 하 다 . 클래스 계층 을 잘 만들 어 놓 고 파생 클래스 가 적절히 가상 함수 를 재정 의 하 면 도형 의 종류 에 상관없이 필요 한 가상 함수 만 호출 하 여 도형 에 따라 다형 적 으로 동작 할 수 있 다 . 가상 함수 를 만들 어 놓 으면 이후 추가 되 는 도형 도 Graphic 으로부터 상속 받 고 가상 함수 만 재정 의 하 면 된다 . 관리 코드 를 완벽 하 게 작성 해 놓 고 클래스 만 늘려 가 면 대 규모 의 프로그램 을 쉽 게 만들 수 있 다 . 
 구현 이 조금 씩 다른 객체 의 집합 을 관리 할 때 는 가상 함수 를 꼭 사용 해야 한다 . 객체 에 따라 달라지 는 동작 을 결정 하 는 작업 은 개발자 가 직접 할 필요 가 없 으며 컴파일러 가 동적 결합 을 위한 모든 준비 를 하 고 실행 중 에 적합 한 함수 를 호출 할 것 이 다 . 가상 함수 를 쓰 기 위해서 는 클래스 계층 이 있 어야 한다 . 그래서 다 형성 의 전제 조건 이 바로 상속인 것 이 다 .