Tuesday, August 12, 2008

Generic Handle Class

Handle Class는 기본 클래스에서 파생된 여러 클래스를 레퍼런스나 포인터를 통해 모두 다룰 수 있도록 캡슐화 해준다. 제대로 짠다면 이러한 Handle Class는 다루는 클래스에 대해 최대한 독립적이여야 한다. 그래서 다루는 클래스 자체를 탬플릿으로 선언하여 Handle Class를 구현하는데 이를 Generic Handle Class라고 한다.

핸들을 사용하기 않고 직접 저수준의 포인터를 사용하게 될 경우 아래와 같은 잠재적인 문제가 있다.

  • 포인터 복사만 하면 객체는 복사가 안된다.
  • 포인터 소멸시켜도 객체는 소멸 안된다.
  • 반대로 포인터를 두고 객체만 소멸시키면 Dangling Pointer가 된다.
  • 포인터 만들고 바인딩 안하고 쓰면 ... 알 수 없는 동작을 한다.

Generic Handle Class는 기본적으로 아래와 같은 기능을 제공한다.

  • Handle을 특정 객체를 참조하는 값으로 생각하면 된다.
  • 객체를 복사할 수 있다(복사 생성자, 대입연산자).
  • 바인딩 여부를 알 수 있다(bool() 연산을 정의하면 됨).
  • virtual 함수 호출을 통해 각 클래스별 메소드를 호출할 수 있다.

우리가 직접 포인터/레퍼런스를 쓰지 않고 핸들 클래스를 사용하기로 했기 때문에 당연히 이후부터는 객체를 포인터를 통해 직접 접근하면 안된다. 그리고 메모리 관리도 Handle Class에서 자동으로 해주므로 Handle을 소멸시키면 Handle에 연결된 객체도 같이 소멸된다.


template<class T> class Handle {
public:
Handle() : p(0) { }
Handle(const Handle& s) : p(0) { if(s.p) p = s.p->clone(); }
Handle& operator=(const Handle&);
~Handle() { delete p; }

Handle(T* t) : p(t) { }

operator bool() const { return p; }
T& operator*() const;
T* operator->() const;

private:
T* p;
};

template<class T>
Handle<T>& Handle<T>::operator=(const Handle& rhs)

if (&rhs != this) {
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}

template<class T>
T& Handle<T>::operator*() const
{
if (p)
return *p;
throw runtime_error("unbound Handle");
}

template<class T>
T* Handle<T>::operator->() const
{
if (p)
return p;
throw runtime_error("unbound Handle");
}


A를 B가 상속받은 경우, Handle <A> h(new B); 식으로 핸들을 생성하면 B클래스가 생성되어 핸들클래스의 포인터에 바인딩된다(virtual 함수를 사용할 수 있다).

operator* 를 정의하므로써 핸들이 가지고 있는 클래스 포인터로 클래스 레퍼런스를 반환하는 역할을 한다.

operator->는 이항연산자 인줄 알았는데 아니란다 ㅡ.ㅡ; 단지 이걸 통해서 오른쪽에 있는 멤버를 접근할 수 있게 해준덴다. 즉, x->y 인 경우 (x.operator->())->y 나 x.p->y와 같덴다. 요건 좀 비직관적인 듯하다.

아무튼 이 두 연산을 통해 Dynamic Binding이 가능해진다. 앗싸.

나는 generic handle class를 만들면 handle class가 필요없는 줄 알았다. Accelerated C++의 내용을 대강 정리해서 어쩌다 제네릭 핸들 클래스를 만들게 되었는지 요약해보면 아래와 같다.

  1. 클래스를 만들었다.
  2. 비슷하지만 다른 클래스도 필요하게 되었다.
  3. 두 클래스를 하나의 코드로 다룰려고 dynamic binding을 사용하려니 포인터/레퍼런스 사용이 복잡하다.
  4. 그래서 두 클래스를 사용하는 핸들 클래스를 만들고 이 핸들을 사용하기로 했다.
  5. 근데 핸들 클래스에도 포인터를 사용하려니 짜증난다.
  6. 포인터를 사용하는 부분만 제네릭 핸들 클래스로 빼버렸다.
  7. 결국 두 클래스 -> 제네릭 핸들 클래스 -> 핸들 클래스 -> 일반 코드 식으로 접근하도록 하여구현하게 되었다.

No comments:

Post a Comment