코딩하는 나귀

저자 : hanburn

날짜 : 2007.08.14

환경 : WTL7.5, VS-2003

 

 

지난 시간에 위자드를 통해서 생성되는 코드들을 살펴 보았습니다. MainDlg의 코드를 살펴보다가 이번시간으로 내용을 넘겼는데, 그 이유는 먼저 살펴 볼 내용이 있어서 입니다. 그럼 시작도록 하겠습니다.

 

 

템플릿에 익숙하지 않은 분들이 계시다면 부지런히 공부를 하시기 바랍니다. ㅎㅎ 이번에는 template의 한가지 사용예제를 먼저 살펴보자. C++ 표준에서는 클래스의 상속리스트로 해당 클래스를 사용 할 수가 있다. 이게 무슨 말인가? 쉽게 아래처럼 사용이 가능하다는 것이다.

 

template <class T>

class B1

{

public:

        void SayHi()

        {

               T* pT = static_cast<T*>(this);

               pT->PrintClassName();

        }

 

        void PrintClassName()

        {

               cout<< "This is B1" << endl;

        }

};

 

class D1 : public B1<D1>

{

 

};

 

class D2 : public B1<D2>

{

public:

        void PrintClassName()

        {

               cout<< "This is D2" << endl;

        }

};

 

 

Class D1 : public B1<D1> 이렇게 템플릿클래스에 자신을 매개변수로 해서 상속을 받을 수가 있는 것이다. 이런 방법은 ATL, WTL에서 자주 사용되는 방법이므로 유의깊게 보기 바란다. 물론 이미 알고 있는사람들은 그냥 대충 넘어가거나 잘못된 내용이 있으면 태클을 걸어주길 바랍니다.^^

D1 SayHi()를 호출하면 D1 PrintClassName을 오버라이드하지 않았기 때문에 베이스인 B1 PrintClassName이 호출이 되고, D2 SayHi()를 호출하면 D2 PrintClassName이 오버라이드 되어서 D2 PrintClassName이 호출되는 것이다. 이건 바로 C++의 다형성을 구현하는 가상함수의 개념이 아닌가? 그런데 한가지 다른 점이 있다. 각각 실행될 때 어떻게 되는지 자세히 살펴보자.

 

먼저 D1의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다.


void B1<D1>::SayHi()

{

D1* pT = static_cast<D1*>(this);


    pT->PrintClassName();

}

 

그리고 D2의 경우에는 템플릿이 아래처럼 컴파일 시간에 확장될 것이다.


void B1<D2>::SayHi()

{

D2* pT = static_cast<D2*>(this);


    pT->PrintClassName();

}

 

중요한 점은 컴파일 시간에 어떤 함수가 불려질지 결정이 된다는 점이다. ? 가상함수는 실행시간에 다형성이 결정되는데, 컴파일 시점에 결정된다면 오히려 안좋은 것이 아닌가? 라고 의문을 가질 수 있는데, 반대로 컴파일 시점에 호출되는 함수가 결정된다는 것은 다음과 같은 이점이 있다.

1.     객체의 포인터를 필요로 하지 않는다.

2.     Vtbls 가 없어서 메모리가 절약되고 수행시간이 더 빠르다.

3.     Vtbl이 초기화되지 않아서 발생할 수 있는 문제점들이 없다. (널참조 같은)

4.     컴파일 시간에 문제점들이 해결될 수 있다.

 

위의 예제는 가상함수를 사용하지 않고 tempalate을 이용하여 다형성을 구현한것이다. 참 흥미로운 부분이다.

 

아래의 예제를 하나 더보도록 하자

 

template<class T>

class CSingleton

{

public:

       

        static T* getInstance(VOID)

        {

               if( TRUE == ::IsBadReadPtr(m_pInstance, sizeof(T)) )

                       m_pInstance = new T();

 

               return m_pInstance;

        }

 

        static VOID releaseInstance(VOID)

        {

               if( FALSE == ::IsBadReadPtr(m_pInstance, sizeof(T)) )

                       delete m_pInstance;

 

               m_pInstance = NULL;

        }

       

 

private:      

        static T* m_pInstance;

};

 

// Init static member

template<class T> T* CSingleton<T>::m_pInstance = NULL;

 

위의 클래스는 디자인패턴에서 Singleton을 구현한 template 클래스이다. 사용은 아래처럼 하면 간단하게 해당 클래스가 Singleton클래스로 동작을 하게 된다.

 

CMyClass : public Singleton<CMyClass>

{

Public:

}

 

이런 CSingeton 같은 template 클래스를 Mix-in class 라고 한다. Mix-in class는 해당 기능을 가지고 있고, 다른 클래스에 템플릿 상속을 통해서 쉽게 기능을 추가 할 수 있는 클래스 이다. WTL에서는 이런 방식으로 템플릿 클래스의 상속을 통해서 기능을 붙이는 방식으로 설계가 되어 있다. 오 놀라워라~

 

~ 그럼 다시 우리의 MainDlg.h를 살펴보면, 여러 가지 기능들을 상속으로 추가하는 모습들을 볼 수 있는데, 하나하나씩 살펴보도록 하자.

 

class CMainDlg :       public CDialogImpl<CMainDlg>,

public CUpdateUI<CMainDlg>,

                       public CMessageFilter,

public CIdleHandler

{

 

 

먼저 CDialogImpl 클래스다. 이것은 CDialog를 실제적으로 구현하고 있는 클래스로 아래와 같은 상속구조를 가지고 있다.

 

CMainDlg : CDialogImpl : CDialgImplBaseT : CWindowImplRoot : TBase, CMessageMap

 

Public을 빼고 그냥 베이스가 되는 클래스를 오른쪽으로 표시한 것이다. Dialog도 윈도우니까 기본적인 윈도우의 구현인 CWindowImpl에서 상속된 것들을 쭉 타고 내려온다. WTL은 인터페이스와 구현을 CWindow CWindowImlp 이라는 2개의 클래스로 구분을 하였다는 것 정도만 알고 넘어가도록 하자. 하부구조를 다 설명하려면 WTL Architecture라는 강좌를 통해서 하도록 하겠다.

 

그 다음으로는 CUpdateUI 라는 클래스를 살펴보자. 이 클래스는 메뉴 등의 UI의 상태를 관리해주는 역할을 하는 것으로, 내부적으로 UI요소들을 map으로 관리하면서, BEGIN_UPDATE_UI_MAP 매크로를 통해서 해당 map을 구성하게 된다. 그리고 UIAddMenuBar(), UIAddToolBar(), UIAddStatusBar() 등의 함수를 통해서 map을 설정하게 되고, UIEnable(), UISetCheck() 등의 함수를 통해서 UI를 관리하게된다. 이것은 나중에 SDI 형태의 프로그램을 볼 때 더 자세하게 다루도록 하겠다.

 

그 다음으로는 CMessageFilter 클래스이다. 이것은 클래스 이름에서 알수 있듯이 메시지를 필터링 할수 있는 인터페이스 클래스이다.

 

class CMessageFilter

{

public:

        virtual BOOL PreTranslateMessage(MSG* pMsg) = 0;

};

 

클래스를 상속 받으면 반드시 PreTranslateMessage 재정의 해주어야 한다. MFC Pretranslate 함수와 같은 역할을 한다. 메시지가 전달되기 전에 먼저 처리해주는 역확을 한다.

 

다음은 CIdleHandler 인데, 이것도 이름에서 바로 있듯이 OnIdle 핸들러를 제공하는 것이다.

 

class CIdleHandler

{

public:

        virtual BOOL OnIdle() = 0;

};

 

그런데 단순히 CMessageFilter CIdlHandler를 상속만 받으면 어떻게 PretranslateMessage OnIdle이 호출될까 라고 궁금하지 안은가? 그 원리는 OnInitialDialog 에 있는 코드에 있다.

 

// register object for message filtering and idle updates

CMessageLoop* pLoop = _Module.GetMessageLoop();

ATLASSERT(pLoop != NULL);

pLoop->AddMessageFilter(this);

pLoop->AddIdleHandler(this);

 

위의 코드가 메시지 루프에 각각을 추가해주기 때문에 메시지를 전달하는 과정에서 수를 부리는 것이다. 이것을 보면 바로 Pretranslate 에는 OnInitialDialog 다음에 오는 메시지만 Filtering을 할 수 있게 된다. WM_INITDIALOG 같은 메시지는 안잡히게 되는 것이다.


그럼 다음에는 메시지 맵에 대해서 알아보도록 하겠습니다. ^^