2015. 2. 5.

[C++]상속과 함수 오버라이딩(Function Overriding)

 상속에 연관되어 살펴볼 만한 간단한 주제 중 하나가 함수 오버라이딩이다. 함수 오버라이딩은 앞으로 만나볼 C++의 다형성(polymorphism)과 가상 함수(virtual)의 출발점과도 같다. 그리고 이참에 함수 오버로딩(function overloading)과 확실히 구분하자.


함수 오버라이딩(function overriding)

 함수 오버라이딩이란 기초 클래스와 동일한 이름의 함수를 유도 클래스에서 정의하는 방식으로 구현이 된다.
 First 클래스에서 정의한 MyFunc() 함수를 자식 클래스 Second에서 다시 정의하고 있다. Second 클래스의 자식 클래스인 Third 클래스에서도 MyFunc() 함수를 다시 정의하고 있다. 이처럼 부모 클래스에서 자식 클래스의 함수를 다시 정의하여 함수 오버라이딩할 수 있다. 하지만 이 설명만으로는 조금 부족한 구석이 있다. 함수 오버로딩(Function Overloading)을 떠올려보자. 함수 오버로딩은 함수의 이름은 동일하고, 매개변수의 자료형이 다르거나 매개변수의 수가 다른 경우 였다. 만약 상속 관계에서 함수 오버로딩을 하면 매개변수에 맞는 오버로딩된 함수가 호출될 것이다. 상속 관계면 무조건 함수 오버라이딩이라고 생각해서는 안 된다. 함수 오버로딩도 상속 관계에서 이뤄질 수 있다는 것을 언급하고 싶었다.

 그럼 위의 클래스들을 참고하고 아래 메인 함수 코드를 살펴보자.
 각 클래스의 객체를 하나씩 생성하고 동일한 이름의 멤버함수인 MyFunc()을 호출하였다. 객체 포인터 참조 관계를 떠올려 보자. 혹시 모르는 사람은 링크를 꼭 살펴보길 바란다. 함수 오버라이딩과 객체 포인터 참조 관계를 모두 이해해야 C++의 다형성을 이해할 수 있다. 다시 돌아와서 tptr가 호출하는 함수는 Thrid 클래스의 MyFunc() 함수이다. sptr, fptr 역시 자료형에 맞는 자신의 멤버함수를 호출할 것이다. 어떻게 부모의 MyFunc() 함수가 아니라 자신의 MyFunc() 함수를 호출할 수 있을까?

 부모 클래스의 멤버함수를 자식 클래스에서 함수 오버라이딩하면 부모 클래스의 멤버함수는 가려지게 된다. 가려진다는 표현이 어색하게 들릴 수도 있다. 원래 부모 클래스의 멤버함수가 있다면 자식 클래스에서 당연히 호출할 수 있다. 하지만 자식 클래스에서 그 함수를 오버라이딩하면 그 멤버함수는 오버라이딩한 멤버함수에 의해 자신이 호출당할 기회를 양보해야 한다. 이를 좀더 비유적으로 말하자면 어떤 멤버함수를 호출하려고 불렀는데, 이 멤버함수는 알고보니 자신을 오버라이딩한 멤버함수가 있었다. 오버라이딩한 함수는 자신과 이름도 같았다. 함수 호출이 이뤄지자 오버라이딩한 멤버함수는 마치 호출된 것처럼 함수를 실행하였다. 이 멤버함수는 오버라이딩한 멤버함수에 의해 가려져서 호출되지 않는다. 그래서 가려졌다고 표현하였다. 결국 부모 클래스의 멤버함수가 오버라이딩되면 그 함수는 자식 클래스의 멤버함수에 의해 가려져서 자식 클래스의 객체가 호출하면 부모 클래스의 멤버함수는 실행되지 않고 자식 클래스에 오버라이딩한 함수가 호출된다.


다형성(polymorphism)으로...

 함수 오버라이딩과 객체 포인터의 참조 관계에 대한 지식을 토대로 다음 코드를 살펴보자.
 이번엔 Third 클래스의 객체를 생성하고, 이를 Third형 포인터, Second형 포인터, First형 포인터로 참조하고 있다. 이 경우 함수 오버라이딩은 어떻게 될까? 객체 포인터의 참조 관계를 잘 떠올려보자. 그럼 답이 나올지도 모른다.

 먼저 만들어진 객체는 Third 클래스의 객체이다. 이 객체는 부모 클래스의 멤버함수 MyFunc()을 오버라이딩하고 있다. 그렇다면 어떤 포인터로 호출하여도 Third 클래스의 MyFunc() 함수가 호출되어야 한고 생각할 수 있다. 이렇게 생각하면 틀렸다. 틀린 원인은 객체 포인터의 참조 관계를 제대로 적용하지 않았기 때문이다.

 객체 포인터의 참조 관계에 따르면 컴파일러는 자료형을 기준으로 포인터 연산의 가능 여부를 따진다. 실제 참조하는 객체가 무엇인지 중요하지 않다. fptr은 First의 멤버함수를,  sptr은 Second의 멤버함수를 호출하려는데 이 함수가 오버라이딩되었으므로 자신의 멤버함수를, tptr는 Third의 멤버함수를 호출하려는데 이 함수가 오버라이딩되었으므로 자신의 멤버함수를 호출한다.


정리하면

  • 상속 관계에서 자식 클래스에서 부모 클래스의 멤버함수와 동일한 이름의 함수를 정의하는 것을 함수 오버라이딩이라고 한다.(단, 함수 오버로딩의 경우과 구별할 수 있어야 한다.
  • 객체 포인터의 참조 관계와 함수 오버라이딩에 대한 기본적인 이해를 토대로 C++의 다형성을 제대로 알 수 있다.

댓글 없음:

댓글 쓰기