U E D R S I H C RSS
ID
Password
Join
사랑은 거창한 것도, 요란한 것도 아닙니다. 강물이 그렇듯, 그저 잘 흘러가주는 것이 사랑입니다. - 고도원의 아침편지 2005년 1월 14일자.

 * 자작 글입니다. :) (드디어 저도 번역을 벗어나서 자작 아티클을 적기 시작했습니다. :) )

발단 #

프로젝트 중에 동적 바인딩을 쓰지 않고 자식 클래스의 메소드를 부모 클래스에서 호출해야할 경우가 생겼습니다. luabind을 쓰다가 생긴 일인데, 물론 virtual이 사용가능하지만, 매번 두개의 클래스를 하나에 바인딩한다는 것이 찜찜해서 직접 손수 구현하게 되었습니다. 물론 장점 단점이 있습니다.

장점 #

  1. virtual을 안써도 된다. 심하면 굳이 상속을 받지 않아도 사용가능.
  2. luabind와 같은 함수 포인터에 민감한 라이브러리에 안심하고 적용가능. (자식클래스에 virtual 키워드 안넣어도 됨)

단점 #

  1. 부모 클래스내에 자식클래스의 종류대로 enum을 선언해야하므로 꽤 성가실 수 있다.
  2. 템플릿을 사용하므로 결과적으로 부모클래스에 상속된 자식클래스 개수 만큼 메소드가 생성된다. (코드 거대화 우려)

예제 #

다음 예제를 통해서 설명하겠습니다. 우선 필요한 헤더화일을 include 합니다.
#include <iostream>
#include <stdlib.h>
#include <typeinfo>

using namespace std;
여기에서는 Base라는 클래스를 부모로 놓고, 자식 클래스는 Derive1, Derive2로 놓도록 하겠습니다. 역호출이 필요한 메소드는 test_derived()입니다. (부모, 자식할 것없이 모두 선언되어있는 것을 볼 수 있습니다) 우선 Base부터 보죠.
struct Base {
  enum child_flag {cf_Derived1, cf_Derived2};
  child_flag _child;

  void test() { cout << "Base::test()" << endl; }
  void test_derived();
};
_child는 자식 클래스에서 설정할 "원래 클래스가 뭐지?"에 대한 표시입니다. 즉, cf_Derived1로 설정되어있으면 이 클래스는 Derived1을 형변환했던 거라고 간주하게 됩니다. 아래 자식클래스의 선언에서 보면 생성자에서 해당 열거형값을 설정하는 것을 볼 수 있습니다. test_derived()메소드는 아래에서 선언합니다. 이 메소드내에서는 Derived1, Derived2 클래스 모두를 사용하기 때문에 차후에 선언합니다. (이 패턴을 쓸때에는 각 클래스를 별도의 cpp화일로 분리하는 것이 좋습니다)
struct Derived1 : public Base {
	Derived1() { _child = Base::cf_Derived1; }
  void test() { cout << "Derived1::test()" << endl; }
  void test_derived() { cout << "바보!"<< endl; }
};

struct Derived2 : public Base {
	Derived2() { _child = Base::cf_Derived2; }
  void test() { cout << "Derived2::test()" << endl; }
  void test_derived() { cout << "바보!"<< endl; }
};
자식클래스의 생성자에서 _child 속성값을 채워넣어 주어야합니다. 이를 사용하여 Base의 test_derived()에서 자신이 원래 무슨 클래스인지를 알수 있게 됩니다.
void Base::test_derived() {
	switch(this->_child) {
	case cf_Derived1 :
 		reinterpret_cast<Derived1*>(this)->test(); break;
	case cf_Derived2 :
 		reinterpret_cast<Derived2*>(this)->test(); break;
	}
}
Base클래스의 test_derived() 맴버는 자신이 원래 어느 자식클래스에서 형변환되었는지를 확인만하고 해당 자식클래스로 자신을 형변환을 한다음 같은 이름의 메소드를 실행합니다. 이제 메인 코드를 보죠.
int main()
{
	Derived1* pd = new Derived1;
	Derived2* pd2 = new Derived2;
	Base* pb = pd;
	Base* pb2 = pd2;

	pb->test_derived();
	pb2->test_derived();

  system("PAUSE");
  return 0;
}
실행해보면 pb, pb2 모두 Base * 데이타 타입으로 선언되어있지만, 정확하게 각각의 Derived1과 Derived2의 메소드를 찾아 실행되는 것을 볼 수 있습니다. 실행결과는 다음과 같습니다.
Derived1::test()
Derived2::test()

결론 #

위 예제에 빗대어 설명하면 이 패턴이 쓸모있을 때에는 Derived1, Derived2 클래스는 스크립트에 바인딩되고, 내부로 따로 처리되는 루틴상에서는 Base *를 요구할 경우 매우 유용합니다. 이때 Base *를 사용하는 클래스 또는 함수 역시 스크립트에 바인딩될 경우에는 별도의 함수를 각 자식클래스 별로 제작하여야만 합니다. (이는 템플릿 함수로 쉽게 선언할 수 있습니다. 가령 Factory라는 클래스가 있고 이 클래스내에 test()라는 메소드의 매개변수가 Derive1, Derive2중 아무거나 넣을 수 있고 각각의 test_derived()를 호출해주어야한다면 다음과 같이 코딩할 수 있을겁니다.
class Factory {
public:
  template<class T>
  void test(T *arg) { reinterpret_cast<Base *>(arg)->test_derived(); }
};
위 패턴을 사용하지 않는다면 형변환시 예외를 감지해서 작성하는 수밖에는 방법이 없는데, 이는 적절한 방식이 아닙니다. 그러므로 동적바인딩을 쓰지 않고 같은 효과를 얻으려면 이 방법밖에는 없습니다.

혹시나 더 좋은 아이디어가 있다면 이메일이나 포럼, 게시판에 남겨주시면 감사하겠습니다.

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2010-10-28 12:42:52
Processing time 0.3520 sec