요즘 회사 내에서 기존에 만들어진 컨트롤들을 새로 만들어 진 컨트롤로 변경하는 작업을 계속 진행 중입니다.
새로 만들어진 콘트롤들은 인터페이스 기반으로 만들어 져 있고 인터페이스는 조금씩 틀리지만 대부분 아래와 같은 선언과 생성, 리소스 반환 방식을 채택하고 있습니다.
1. 선언
IXXControlA* m_pControl;
2. 생성
m_pControl = NULL;
m_pControl = GetControlA();
3. 종료 함수
if (m_pControl)
m_pControl->Release();
와 같은 방식으로 사용하고 있습니다.
컨트롤들의 종료는 수십 가지이지만, 컨트롤을 생성하는 방식과, 모두 다 사용한 후 리소스를 반환하는 방식(->Release() 호출) 은 모두 동일합니다.
새로 만들어진 컨트롤을 이용해서 기존 컨트롤을 하나 둘씩 대체해 나가다 보니, 컨트롤의 종류와 상관없이 반복적으로 사용되는 (하지만 메모리 반환을 위해 꼭 필요한) 아래와 같은 작업들이 귀찮아졌습니다. 게다가 혹시나 리소스를 반환하지 않을 경우 또는 NULL 체크를 제대로 하지 못한 경우 프로그램을 죽게도 만들 수 있기 때문에 작업에 많은 주의가 필요했습니다.(하루에도 수백개씩 바꾸다 보면 기계적으로 해 줄 수 있는 뭔가가 필요해 지더군요)
// 초기화
m_pControl = NULL;
// 리소스반환
if (m_pControl)
m_pControl->Release();
그래서 auto_ptr 이나 CComPtr 같은 포인트를 감싸는 간단한 클래스를 하나 만들면 어떨까 생각했습니다.
별 생각없이 간단하게 만든 템플릿은 아래와 같습니다.
template <class T>
class INewControlPtr
{
public:
INewControlPtr(){m_pCtl = NULL;}
~INewControlPtr()
{
if (m_pCtl)
m_pCtl->Release();
m_pCtl = NULL;
}
T* m_pCtl;
}
생성자와 소멸자에서 적당히 초기화와 리소스 반환을 해 주고 있어서 1차적인 작업들은 완료 되었습니다.
이제 사용자는
IXXControlA* m_pControl;
대신에
INewControlPtr<IXXControlA> m_pControl;
과 같이 선언할수 있습니다.
하지만 IXXControlA* 포인터가 필요한 곳에 INewControlPtr<> 템플릿이 타입 변환없이 쓸 수 있도록 하기 위해서는 여러가지 연산자들은 오버로딩해 줘야 했습니다.
가장 우선적으로 IXXControlA* 가 필요할 때 INewControlPtr<> 대신 실제 컨트롤인 m_pCtl 를 리턴하는 함수가 필요했습니다.
그리고 혹시 m_pCtl 에 값을 할당할 수도 있기 때문에 INewControlPtr** 로 변환할 수 있는 연산자도 오버로딩했습니다.
T** operator&() throw()
{
ATLASSERT(m_pControl==NULL);
return &m_pControl;
}
사용하다보면 NULL 검사를위해
처럼사용할수도있기때문에 Not 연산자(!)도필요합니다
bool operator!() const throw()
{
return (m_pCtl == NULL);
}
* 연산자를 통해 포인터의 값을 요구할 수도 있습니다. 해당 연산자도 오버로딩했습니다.
T& operator*() const
{
ASSERT(m_pCtl);
return *m_pCtl;
}
당연히 할당 연산자(=)에 대해서도 필요합니다
T* operator =(T* data) {m_pCtl = data; return m_pCtl;} // = 연산자오버로딩
가끔은 컨트롤을 가리키는 포인트가 서로 같은지 혹은 NULL 인지 물어 보는 == 연산자도 사용하게 됩니다. 아래와 같이 == 연산자에 대한 오버로딩 처리를 했습니다.
bool operator==( T* pT) const throw()
{
return m_pCtl == pT;
}
m_pControl->DoSomething(); 과 같이 -> 연산자를 호출하여 IXXControlA* 의 멤버 함수를 호출해야 할 경우가 많습니다. 이를 위해 -> 연산자를 아래와 같이 오버로딩했습니다.
T* operator->() {return m_pCtl;} // -> 연산자오버로딩
그리해서 만든 전체 템플릿 소스는 아래와 같습니다.
template <class T>
class INewControlPtr
{
public:
INewControlPtr(){m_pCtl = NULL;}
~INewControlPtr()
{
if (m_pCtl)
m_pCtl->Release();
m_pCtl = NULL;
}
T* m_pCtl;
// 연산자오버로딩- IXXControlA 을요청하는경우IXXControlA 로자연스럽게변환
operator T*() const {return m_pCtl;}
// 포인터(m_pCtl) 자체에값을할당할경우에는T** 파라미터가필요하다.
T** operator&() throw()
{
ATLASSERT(m_pCtl==NULL);
return &m_pCtl;
}
bool operator!() const throw()
{
return (m_pCtl == NULL);
}
T& operator*() const
{
ASSERT(m_pCtl);
return *m_pCtl;
}
T* operator =(T* data) {m_pCtl = data; return m_pCtl;} // = 연산자오버로딩
bool operator==( T* pT) const throw()
{
return m_pCtl == pT;
}
T* operator->() {return m_pCtl;} // -> 연산자오버로딩
};
처음 생각에는 생성자와 소멸자만 대신하는 간단한 클래스를 만들려고 했는데, 만들다 보니 하나씩 함수가 늘어 나게 되더군요.
만들면서 ATL 에 있는 CComPtr<> 소스를 많이 참조하게 되었습니다. CComPtr<> 소스를 보니 아직도 구현해야 하는 함수가 많더군요. INewControlPtr <> 끼리 복사 연산자도 구현해야 하고, 원치 않는 오퍼레이션에 대한 방어도 구현해야 하는 등, 미비한 점이 많습니다.
포인터를 간단하게 감싸면서 포인터를 필요한 곳에 손쉽게 자신을 넘겨주는 클래스를 만드는 것이 생각한 것보다는 조금 힘들었지만 만들고 났더니, 한결 코딩하기가 편해졌습니다. 게다가 리소스를 실수로라도 잃어버리는 문제도 자동으로 없어졌고, 코딩량도 조금 줄었으니 1석 3조네요.
많은 C++책에서 Smart Pointer 나, auto_ptr 같은 클래스를 사용하라고 권장하는데, 실제로도 만들어 쓰다 보면 많은 작업에서 효율이 증가하는걸 느낍니다.
늘 겪는 일이지만, 머리보다, 손이 너무 빠르면, 일이 빨리 진행되는 듯 하지만, 반복적인 작업이 늘어나거나, 추후 문제를 일으킬 가능성이 증가하곤 합니다.
잠시 코딩을 멈추고, 한 박자 쉬더라도 머리 속으로 좀 더 나은 코딩을, 인생을 생각해 보는 건 어떨까요.
'개발 > C C++' 카테고리의 다른 글
[C/C++]자주 하는 실수 (15) | 2008.10.22 |
---|---|
[C++]소멸자에서 가상함수 호출하기 (8) | 2008.10.08 |
[C/C++] enum, 보다 나은 enum (19) | 2008.07.21 |
싱글톤 클래스 (5) | 2008.07.01 |
[C++]STL Container 조합하기 (5) | 2007.11.12 |
[C/C++]유용한 #pragma directive (9) | 2007.09.05 |
댓글