MFC六大關鍵技術之剖析(第三部分)

三、動態創建

動態創建就是運行時創建指定類的對象,在MFC中大量使用。如框架窗口對象、視對象,還有文檔對象都需要由文檔模板類對象來動態的創建。我覺得這是每個MFC的學習者很希望理解的問題。

初次接觸MFC的時候,很容易有這樣的迷惘。MFC的幾大類不用我們設計也就罷了,但最疑惑的是不用我們實例化對象。本來最直觀的理解就是,我們需要框架的時候,親手寫上CFrameWnd myFrame;需要視的時候,親自打上CView myView;……

但MFC不給我們這個機會,致使我們錯覺窗口沒有實例化就彈出來了!就象畫了張電視機的電路圖就可以看電視一樣令人難以置信。但大夥想了一下,可能會一拍腦門,認爲簡單不過:MFC自動幫我們完成CView myView之流的代碼不就行了麼!!!其實不然,寫MFC程序的時候,我們幾乎要對每個大類進行派生改寫。換句話說,MFC並不知道我們打算怎樣去改寫這些類,當然也不打算全部爲我們“靜態”創建這些類了。即使靜態了創建這些類也沒有用,因爲我們從來也不會直接利用這些類的實例幹什麼事情。我們只知道,想做什麼事情就往各大類裏塞,不管什麼變量、方法照塞,塞完之後,我們似乎並未實例化對象,程序就可以運行!

要做到把自己的類交給MFC,MFC就用同一樣的方法,把不同的類一一準確創建,我們要做些什麼事情呢?同樣地,我們要建立鏈表,記錄各類的關鍵信息,在動態創建的時候找出這些信息,就象上一節RTTI那樣!我們可以設計一個類:

struct CRuntimeClass{
    LPCSTR m_lpszClassName; //類名指針
    CObject* (PASCAL *m_pfnCreateObject)(); //創建對象的函數的指針
    CRuntimeClass* m_pBaseClass; //講RTTI時介紹過
    CRuntimeClass* m_pNextClass; //指向鏈表的下一個元素
    CObject* CreateObject(); //創建對象
    static CRuntimeClass* PASCAL Load(); //遍歷整個類型鏈表,返回符合動態創建的對象。
    static CRuntimeClass* pFirstClass; //類型鏈表的頭指針
};

一下子往結構裏面塞了那麼多的東西,大家可以覺得有點頭暈。至於CObject* (PASCAL *m_pfnCreateObject)();,這定義函數指針的方法,大家可能有點陌生。函數指針在C++書籍裏一般被定爲選學章節,但MFC還是經常用到此類的函數,比如我們所熟悉的回調函數。簡單地說m_pfnCreateObject即是保存了一個函數的地址,它將會創建一個對象。即是說,以後,m_pfnCreateObject指向不同的函數,我們就會創建不同類型的對象。

有函數指針,我們要實現一個與原定義參數及返回值都相同一個函數,在MFC中定義爲:

static CObject* PASCAL CreateObject(){return new XXX};//XXX爲類名。類名不同,我們就創建不同的對象。

由此,我們可以如下構造CRuntimeClass到鏈表:

CRuntimeClass classXXX={
    類名,
        ……,
        XXX::CreateObject()   //m_pfnCreateObject指向的函數
        RUNTIME_CLASS(基類名)  // RUNTIME_CLASS宏可以返回CRuntimeClass對象指針。
        NULL                    //m_pNextClass暫時爲空,最後會我們再設法讓它指向舊鏈表表頭。
};

這樣,我們用函數指針m_pfnCreateObject(指向CreateObject函數),就隨時可new新對象了。並且大家留意到,我們在設計CRuntimeClass類對時候,只有類名(和基類名)的不同(我們用XXX代替的地方),其它的地方一樣,這正是我們想要的,因爲我們動態創建也象RTTI那樣用到兩個宏,只要傳入類名和基類作宏參數,就可以滿足條件。

即是說,我們類說明中使用DECLARE_DYNCREATE(CLASSNMAE)宏和在類的實現文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏來爲我們加入鏈表,至於這兩個宏怎麼爲我們建立一個鏈表,我們自己可以玩玩文字代換的遊戲,在此不一一累贅。但要說明的一點就是:動態創建宏xxx_DYNCREATE包含了RTTI宏,即是說, xxx_DYNCREATE是xxx_DYNAMIC的“增強版”。

到此,我們有必要了解一下上節課沒有明講的m_pNextClass指針。因爲MFC層次結構是樹狀的,並不是直線的。如果我們只有一個m_pBaseClass指針,它只會沿着基類上去,會漏掉其它分支。在動態創建時,必需要檢查整個鏈表,看有多少個要動態創建的對象,即是說要從表頭(pFirstClass)開始一直遍歷到表尾(m_pNextClass=NULL),不能漏掉一個CRuntimeClass對象。

所以每當有一個新的鏈表元素要加入鏈表的時候,我們要做的就是使新的鏈表元素成爲表頭,並且m_pNextClass指向原來鏈表的表頭,即像下面那樣(當然,這些不需要我們操心,是RTTI宏幫助我們完成的):

pNewClass->m_pNextClass=CRuntimeClass::pFirstClass;//新元素的m_pNextClass指針指向想加入的鏈表的表頭。
CRuntimeClass::pFirstClass=pNewClass;//鏈表的頭指針指向剛插入的新元素。

好了,有了上面的鏈表,我們就可以分析動態創建了。

有一了張有類名,函數指針,動態創建函數的鏈表,我們就可以知道應該按什麼步驟去動態創建了:1、獲得一要動態創建的類的類名(假設爲A)。2、將A跟鏈表裏面每個元素的m_lpszClassName指向的類名作比較。3、若找到跟A相同的類名就返回A所屬的CRuntimeClass元素的指針。4、判斷m_pfnCreateObject是否有指向創建函數,有則創建對象,並返回該對象。代碼演示如下(以下兩個函數都是CRuntimeClass類函數):

///////////////以下爲根據類名從表頭向表尾查找所屬的CRuntimeClass對象////////////

CRuntimeClass* PASCAL CRuntimeClass::Load()
{    
    char szClassXXX[64];
    CRuntimeClass* pClass;
    cin>>szClassXXX;      //假定這是我們希望動態創建的類名
    for(pClass=pFirstClass;pClass!=NULL;pClass=pClass->m_pNextClass)
    {
        if(strcmp(szClassXXX,pClass->m_lpszClassName)==0)
            return pClass;
    }
    return NULL
}

///////////根據CRuntimeClass創建對象///////////

CObject* CRuntimeClass::CreateObject()
{
    if(m_pfnCreateObject==NULL) return NULL;
    CObject *pObject;
    pObject=(* m_pfnCreateObject)();              //函數指針調用
    return pObject;                                  
}

有了上面兩個函數,我們在程序執行的時候調用,就可以動態創建對象了。
我們還可以更簡單地實現動態創建,大家注意到,就是在我們的程序類裏面有一個RUNTIME_CLASS(class_name)宏,這個宏在MFC裏定義爲:
RUNTIME_CLASS(class_name)  ((CRuntimeClass*)(&class_name::class##class_name))
作用就是得到類的RunTime信息,即返回class_name所屬CRuntimeClass的對象。在我們的應用程序員類(CMyWinApp)的InitInstance()函數下面的CSingleDocTemplate函數中,有:
RUNTIME_CLASS(CMyDoc),
RUNTIME_CLASS(CMainFrame),       // main SDI frame window
RUNTIME_CLASS(CMyView)
構造文檔模板的時候就用這個宏得到文檔、框架和視的RunTime信息。有了RunTime信息,我們只要一條語句就可以動態創建了,如:
classMyView->CreateObject();      //對象直接調用用CRuntimeClass本身的CreateObject()
現在,細心的朋友已經能清楚動態創建需要的步驟:

1、定義一個不帶參數的構造函數(默認構造函數);因爲我們是用CreateObject()動態創建,它只有一條語句就是return new XXX,不帶任何參數。所以我們要有一個無參構造函數。
2、類說明中使用DECLARE_DYNCREATE(CLASSNMAE)宏;和在類的實現文件中使用IMPLEMENT_DYNCREATE(CLASSNAME,BASECLASS)宏;這個宏完成構造CRuntimeClass對象,並加入到鏈表中。
3、使用時先通過宏RUNTIME_CLASS得到類的RunTime信息,然後使用CRuntimeClass的成員函數CreateObject創建一個該類的實例。
4、CObject* pObject = pRuntimeClass->CreateObject();//完成動態創建

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/liuliye23/archive/2009/12/15/5008658.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章