C++ 가상 함수(2)

3738 단어 CC

가상 함수(1)에 이어서 설명하면, 가상 함수의 동작을 이해하기 위해선
바인딩(Binding)에 대한 이해가 필요하다.

바인딩(Binding)

  • 변수나 함수 등 코드 내의 선언과 정의를 확정지는 것

바인딩을 정확하게 설명하는 것이 애매해서 위와 같이 정의했다.
그냥 "확정지는 것"이라는 말만 기억하면 된다.

바인딩은 정적 바인딩동적 바인딩으로 나뉜다.

  • 정적 바인딩
  1. 컴파일 단계에서 선언과 정의가 확정되는 것을 의미한다.
  2. 가상 함수로 선언되지 않은 일반 함수들이 이에 해당한다.
  • 동적 바인딩
  1. 선언과 정의가 확정되는 것이 연기되어 프로그램 실행 시간에 결정되는 것을 의미한다.
  2. 가상 함수로 선언된 함수들이 이에 해당한다.

위의 개념에 이어서 말하자면,
선언과 정의가 확정됨에 따라 어떤 변수나 함수를 호출할 수 있는지 알게 된다는 것에서 두 개념의 차이가 있다.

아직 감이 잘 오지 않을 것이다. 아래 그림을 보자.

Mom 클래스의 shout() 함수가 virtual로 선언되면서 Son 클래스의 shout() 함수를 가리키는 포인터가 생겼다.
따라서, 가상 함수로 선언하면 해당 함수를 가리키는 포인터가 서브 클래스에 오버라이딩된 함수를 가리키게 된다는 것을 직관적으로 알 수 있을 것이다.

여기서, 동적 바인딩이 어떻게 이루어 지는지 보다 더 자세하게 이해하기 위해 가상 함수의 구현이 어떻게 되는지 이해할 필요가 있다.

가상 함수를 멤버로 가지는 클래스는 컴파일 시 "가상 함수 테이블(V-Table)"이 생성되고 객체 생성 시 그 테이블의 주소값 정보를 객체가 포함하게 된다.
V-Table에는 함수의 이름과 함께 주소값이 저장되어 있는데
이때, 해당 함수를 서브 클래스에서 오버라이딩(Overriding)하게 되면 해당 함수와 이름은 같지만, 주소값이 다른 값으로 저장된다.

이해를 위해 다음 코드와 그림을 보자.(그림은 대략적으로 표현한 것입니다.)

#include <iostream>
using namespace std;

class Mom {
public:
    virtual void shout() {
    	cout << "Do your homework!" << endl;
    }
    virtual void func() {}
};

class Son:: public Mom {
public:
    void shout() {
    	cout << "I want to play a game" << endl;
    }
};


위와 같이 슈퍼 클래스(Mom)의 shout() 함수를 서브 클래스(Son)에서 오버라이딩함에 따라 Son 클래스의 V-Table에서 해당 함수의 주소값이 슈퍼 클래스와 다르게 저장되는 것을 알 수 있다.
반대로, 같은 가상 함수인 func() 함수는 슈퍼 클래스의 주소값이 유지되는 것을 알 수 있다.

다시 main() 함수를 보자.

int main() {
    Mom *m;
    Son s;
    m = &s;
   
    m.shout(); // OK
        
    return 0;
}

현재 객체 m은 Son 클래스의 객체를 Upcasting하여 정의된 것으로 Son Class의 V-Table에 대한 정보를 가지고 있다.
따라서, 객체 m을 통해 shout() 함수를 호출하게 되면 Son 클래스의 shout()함수가 호출된다.

마무리 하며...

가상 함수편(1) 서두에 말한 것처럼 이번 포스팅을 작성하면서 가상함수의 목적 그리고 오버라이딩(Overriding)과의 차이점이 무엇인지 생각했다.
내가 이해한 바로는 다음과 같다.

  • 가상 함수의 목적은 다형성 실현이다.
    가상 함수를 사용하게 되면 Upcasting이 일어난 조건에서도 객체의 특성에 맞게 함수를 구현할 수 있다는 점에서 다형성을 실현할 수 있다는 것으로 이해했다.
  • 오버라이딩(Overring)과는 바인딩(Binding)에서 차이가 있다.
    일반적인 오버라이딩의 경우, 정적 바인딩이 이루어지고
    가상 함수로 선언되어 오버라이딩된 경우, 동적 바인딩이 이루어진다.
    (결과적으로 둘의 차이가 있다는 의문은 별 의미가 없던 것 같다.)

이외에도 C++에서 가상 함수를 사용하는 또 다른 이유 중에 "소멸자"와 관련이 있다.
예를 들어, 슈퍼 클래스의 생성자에서 동적 할당을 받고 서브 클래스의 생성자에서도 동적 할당을 받는 코드가 있다고 하자.
여기서, 슈퍼 클래스의 소멸자를 virtual로 선언하지 않으면 서브 클래스의 소멸자가 실행되지 않아 메모리 누수가 일어날 수 있다.

C++ 가상 함수 [끝]

  • 읽어 주셔서 정말 감사드립니다.
  • 오타나 잘못된 정보를 댓글로 남겨주시면 정말 감사하겠습니다.

좋은 웹페이지 즐겨찾기