[C/C++] 동적 바인딩, virtual의 사용

개발 노트 2009. 1. 28. 15:19 posted by 무병장수권력자


동적 바인딩 (Dynamic Binding)

작성자 : 김문규
최초 작성일 : 2009. 1.28

1. 들어가며
C++은 다형성을 지원하는 객체 지향 언어입니다. overriding을 통해 함수 재정의가 가능하지요. 그런데 복잡한 상속 개념은 때로는 우리가 기대하지 않는 방향으로 프로그램을 동작하게 합니다. 동적 바인딩은 그 대표적인 예 입니다.

2. 나의 기대

#include <iostream>
using namespace std;
class CParent
{
public:
 CParent() { cout << "Creating Parent" << endl; };
 ~CParent() { cout << "Deleting Parent" << endl; }
 void DoSomething1() { cout << "Doing something 1 in Parent" << endl; }
 void DoSomething2() { cout << "Doing something 2 in Parent" << endl; }
};
class CChild : public CParent
{
public:
 CChild() { cout << "Creating Child" << endl; };
 ~CChild() { cout << "Deleting Child" << endl; }
 void DoSomething1() { cout << "Doing something 1 in Child" << endl; }
 void DoSomething2() { cout << "Doing something 2 in Child" << endl; }
};
int main(){
 
 cout << "Case 1 : parent class를 그대로 사용할 경우" << endl;
 CParent *pParent1 = new CChild();
 pParent1->DoSomething1();
 pParent1->DoSomething2();

 delete (pParent1);
 return 0;
}

인터페이스처럼 사용해볼 요량으로 빨간색처럼 코딩을 했습니다. 실체는 CChild이기 때문에 파란색으로 표현된 코드에서는 CChild의 DoSomething2()가 호출될 것으로 생각했습니다. 결과는 아래와 같습니다. 제 기대와는 딴판이었지요. 이런! 메모리 해제도 제대로 되지 않았네요. 흠 좌절입니다...



3. 동적 바인딩
앞에서의 오동작의 이유는 pParent 객체 포인터가 동적 바인딩이 되지 않았기 때문입니다. C++은 기본적으로는 정적 바인딩을 지원합니다. 즉, 해당 객체를 담는 변수에 따라서 컴파일 시에 정적으로 링크가 되어 버린다는 뜻이지요. pParent는 실제로는 CChild를 가리키고 있지만 CParent 타입의 포인터이기 때문에 pParent는 CParent 객체를 가리키고 있는 것이지요.
그럼 어떻게 해야 할까요? virtual 키워드로 함수를 선언하면 됩니다.
virtual로 선언된 경우에는 런타임에 동적으로 바인딩되기 때문에 pParent가 가리키고 있는 CChild의 함수를 호출하게 됩니다.

비교를 쉽게 하기 위해서 DoSomething1()만 virtual로 선언해 보았습니다.
class CParent
{
public:
 CParent() { cout << "Creating Parent" << endl; };
 ~CParent() { cout << "Deleting Parent" << endl; }
 virtual void DoSomething1() { cout << "Doing something 1 in Parent" << endl; }
 void DoSomething2() { cout << "Doing something 2 in Parent" << endl; }
};

결과는 이렇습니다. 오호~ 진작 이랬어야죠. (근데 아직도 소멸자는 제대로 호출이 안되네요. TT)



4. 소멸자 동적 바인딩
여기서 하나의 의문을 더 해결해 보겠습니다. 왜 Child의 소멸자는 호출되지 못했을까요? 이유는 앞선 예들과 같습니다. 정적바인딩 되어 있는 경우에는 CParent 객체의 소멸자만 호출하면 되기 때문이지요. 따라서, CChild의 소멸자가 제대로 호출되게 하려면 소멸자를 virtual 키워드로 선언해야 합니다.
class CParent
{
public:
 CParent() { cout << "Creating Parent" << endl; };
 virtual ~CParent() { cout << "Deleting Parent" << endl; }
 virtual void DoSomething1() { cout << "Doing something 1 in Parent" << endl; }
 void DoSomething2() { cout << "Doing something 2 in Parent" << endl; }
};

이제야 드디어 의도한 대로 동작하게 되었습니다.



5. 마치면서
대부분 상속을 압니다. 단순하게 상속만 알아서 그 개념만 사용하면 문제가 안되지요. 위의 예들도
CChild *pChild = new CChild()
로 선언했다면 아예 문제가 되지 않았을 겁니다.
하지만, CParent로 선언해야 하는 경우가 있을 겁니다. 객체 추상화를 시도하는 경우가 좋은 예일 겁니다.
이처럼 C++의 객체 지향의 속성을 점점 알아가면서 하나둘씩 적용하다 보면 의도하지 않던 오류가 발생하기도 합니다. 그래서 항상 기본이 중요한 것 같네요.
그럼 즐프하세요!