MFC原理第三講.RTTI運行時類型識別


              MFC原理第三講.RTTI運行時類型識別

一丶什么是RTTI

    RTTI. 運行時的時候類型的識別. 運行時類型信息程序.能夠使用基類(父類)指針 或者引用 來檢查這些指針或者引用所指的對象. 實際派生的類型

  簡單來說就是 使用父類指針檢查這個對象是屬於哪個類.

1.本篇博客需要弄清的問題

  1.1 MFC為什么要構建RTTI

  1.2 DECLARE_DYNAMIC 宏

  1.3 IMPLEMENT_DYNAMIC 宏

  1.4 RUNTIME_CLASS 宏

  1.5 CRuntime Class 結構體

2.簡單了解關鍵字的使用

  1.類中定義的static的變量的作用以及怎么初始化.

  2.類中定義的const的變量的作用以及怎么初始化

  3.類中的 static + const定義的變量的作用以及怎么初始化.

二丶C++簡單的RTTI運行類型識別

  在講解我們要搞清楚的問題的時候.寫一個簡單的小例子. 使用C++自帶的 編譯時的RTTI程序.  注意是編譯時.

具體做法:

  1. 要使用typeid 關鍵字  typeid(對象)  typeid (類名)

  2.首先要包含頭文件 <typeinfo.h>

  3.啟動C++自己的類型.  屬性 -> 配置屬性 -> C/C++ -> 語言 -> 啟用運行時類型信息    命令行加的是 /GR 我們也可以直接加/GR

例如下圖:

  

 

1. 首先我們創建兩個類. 一個是 CDog 一個是CCat. 

2.然后我們的Main函數 定義 CDog 對象以及CCat對象. 並且判斷 CDog對象是否屬於CDog這個類.

3.如果屬於我們則進行打印.否則相反.

具體代碼:

  

#include <stdio.h>
#include <typeinfo.h>
#include "Dog.h"
#include "Cat.h"
int main(int argc, char *argv[])
{
    CDog dog;
    CCat cat;
    if (typeid(dog) == typeid(CDog)) 判斷dog對象是否屬於 CDog類   主要就是這行代碼
    {
        printf("是屬於這個對象\r\n");
    }
    else
    {
        printf("不是屬於這個對象\r\n");
    }

    system("pause");
}

實現應用截圖:

  

這個就是簡單的RTTI運行時類型識別了.

 

三丶類中的 static關鍵字.const關鍵字 static + const 關鍵字的作用以及初始化

  1.static關鍵字 修飾的變量.外部進行初始化.並且不依賴與對象.也就是說直接  類名::變量  則可以調用   類型  類名::成員變量  = 值;

  2.const關鍵字修飾的變量只能讀不能改.  初始化的時候必須在 類的構造的初始化列表進行初始化.

  3.static + const修飾的變量. 只能讀不能改. 類名直接調用.  初始化的時候在外部進行初始化  const 類型  類名::成員變量  = 值;

四丶MFC為什么自己構建RTTI

  MFC因為出現的年代比較早.所以自己實現了RTTI. 而且依靠的就是兩個宏

  1.2 的 DECLARE_DYNAMIC 宏

  1.3 的 IMPLEMENT_DYNAMIC 宏

其中1.3里面的宏也包含了一個關鍵的宏 RUNTIME_CLASS 以及關鍵結構體 CRuntime Class

一丶使用1.2 宏 1.3宏.

  現在我們要讓我們自己的類擁有RTTI運行時類型識別.

  步驟:

    1.我們的自己的WinApp 類里面 定義 DECLARE_DYNAMIC宏

    2.main函數之前使用 IMPLEMENT_DYNAMIC 宏

    3.使用 IsKindOf(CRuntime Class *) 來判斷是否是繼承父類. 因為是 CRuntime Class * 所以 我們要使用宏包含我們的 父類 RUNTIME_CLASS(父類)

    4.如果是繼承父類 則返回1. 否則 返回0

知道步驟了.那么打開第一篇博客的代碼. 自己實現的窗口程序.去編寫.

  1.自己類里面定義DECLARE_DYNAMIC宏 截圖:

    

   2.自己類的實現文件中 定義IMPLEMENT_DYNAMIC(自己的類名,父類類名)

      

  3.自己的類已經擁有了Rtti 類型識別.使用Rtii 運行識別.

二丶 RUNTIME_CLASS 宏解析

  上面我們使用 RUNTIME_CLASS 宏來做使用那么我們看一下MFC怎么定義的.

#define _RUNTIME_CLASS(class_name) ((CRuntimeClass*)(&class_name::class##class_name))

其實也是一個宏.不過我們可以拆解一下.

  

#define _RUNTIME_CLASS(CWinApp) ((CRuntimeClass*)(&CWinApp::classCWinApp))

 其中 ##代表了鏈接的意思 也就是 Class##ClassName 可以變成  ClassWinApp

  所以上面我們用宏寫的代碼下方可以替換成我們解析出來的宏.

返回了一個 CRuntime Class * . 返回的是CWinAPP里面的一個成員的地址.因為前邊有一個取地址符號..

首先看一下CRuntimeClass結構體吧

三丶CRuntimeClass結構體

struct CRuntimeClass
{
// Attributes
    LPCSTR m_lpszClassName;           類名
    int m_nObjectSize;                      類的大小
    UINT m_wSchema;                      加載類的編號
    CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
    CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
    CRuntimeClass* m_pBaseClass;
#endif

// Operations
    CObject* CreateObject();
    BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;  判斷函數

    // dynamic name lookup and creation
    static CRuntimeClass* PASCAL FromName(LPCSTR lpszClassName);
    static CRuntimeClass* PASCAL FromName(LPCWSTR lpszClassName);
    static CObject* PASCAL CreateObject(LPCSTR lpszClassName);
    static CObject* PASCAL CreateObject(LPCWSTR lpszClassName);

// Implementation
    void Store(CArchive& ar) const;
    static CRuntimeClass* PASCAL Load(CArchive& ar, UINT* pwSchemaNum);

    // CRuntimeClass objects linked together in simple list
    CRuntimeClass* m_pNextClass;       // 執向下一個CRunTimeClass
    const AFX_CLASSINIT* m_pClassInit;
};

因為上面成員較多.下方簡化一下.

  

struct CRuntimeClass
{
// Attributes
    LPCSTR m_lpszClassName;           類名
    int m_nObjectSize;                      類的大小
    UINT m_wSchema;                      加載類的編號
    ...
    BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;  判斷函數是否是父類
    ...
    CRuntimeClass* m_pNextClass;       // 執向下一個CRunTimeClass
    
};

這個是一個鏈表.  是記錄類型的一個結構. 因為是鏈表.所以可以進行檢查.

四丶DECLARE_DYNAMIC 宏解析

  其實 DECLARE_DYNAMIC 宏也是一個文字替換的東西.我們可以看下代碼.

#define DECLARE_DYNAMIC(class_name) \
public: \
    static const CRuntimeClass class##class_name; \
    virtual CRuntimeClass* GetRuntimeClass() const; \


替換后是下邊的代碼.


public: 
    static const CRuntimeClass classCMyApp; 
    virtual CRuntimeClass* GetRuntimeClass() const; 

在我們的CMyAPP 里面的宏則可以直接去掉了.

做的事情;

  1.申明了 static const 全局可讀的變量. 也就是一個 類型記錄信息結構體 CRuntimeClass 結構

     問題:  既然是static const定義的.那么肯定是在外面進行初始化的.也就是我們的

IMPLEMENT_DYNAMIC 宏.而這個宏內部還包含了宏.並且對我們添加了默認參數.

#define IMPLEMENT_RUNTIMECLASS(class_name, base_class_name, wSchema, pfnNew, class_init) \
    AFX_COMDAT const CRuntimeClass class_name::class##class_name = { \
        #class_name, sizeof(class class_name), wSchema, pfnNew, \
            RUNTIME_CLASS(base_class_name), NULL, class_init }; \
    CRuntimeClass* class_name::GetRuntimeClass() const \
        { return RUNTIME_CLASS(class_name); }

我們簡化一下. 其實也是一個文字替換游戲.

const CRuntimeClass CMyApp::classCMyApp = 
{
        "CMyApp", sizeof(class CMyApp), NULL, NULL, 
        RUNTIME_CLASS(CWinApp), NULL, NULL
}; 
CRuntimeClass* CMyApp::GetRuntimeClass() const 
{ 
    return RUNTIME_CLASS(CMyApp);
}

其實就是對我們的static + const 變量進行初始化.

上圖內部還有一個RUNTIME_CLASS 我們因為上面解析過了RUNTIME_CLASS 知道了.其實是獲取 CWinAPP::classCWinapp 成員.

所以可以繼續進行替換.

替換完如下.

  

const CRuntimeClass CMyApp::classCMyApp = 
{
        "CMyApp", sizeof(class CMyApp), NULL, NULL, 
        (CRuntimeClass*)(&CWinApp::classCWinApp), NULL, NULL
}; 
CRuntimeClass* CMyApp::GetRuntimeClass() const 
{ 
    return(CRuntimeClass*)(&CMyApp::classCWinApp);
}

代碼截圖:

  

我們的IMPLEMENT_DYNAMIC宏就等價於下面的代碼了.

  1. 初始化類內部的 CRuntimeClass 結構的成員.

  2.添加了一個獲取父類的ClassCWinAPP的成員了.

 

五丶RTTI總結

  根據第四小節.我們已經把所有宏的本身模樣還原出來了.看的雜亂無章.但是總結一下很簡單.

  1.  首先有一個結構叫做CRuntimeClass. 里面存儲了類型說明. 比如類名稱.大小. 以及判斷是否是父類....

  2. 有一個宏叫做 DECLARE_DYNAMIC宏. 這個宏就是定義了一個 自己的一個CRuntimeClass 結構的成員.並且添加了一個獲取自己這個成員的一個虛函數.

  3. 實現宏IMPLEMENT_DYNAMIC 其實就是對DECLARE_DYNAMIC 中定義的CRuntimeClass成員進行初始化.  並且實現了 獲取自己這個成員的虛函數.

六丶RTTI中運行時類型識別的方法解析

  上方我們講了RTTI 以及CRuntimeClass 以及兩個宏的總結. 那么我們要使用就是使用 isKindOf來使用. 那么解析下這個函數怎么使用了.

    1.取出我們自己當前對象的 ClassCMyWinApp 指針.

    2.循環遍歷是否等於父類

    3.不等於父類則進行遍歷. 因為CRuntimeClass是一個鏈表結構. 一直進行遍歷.地址比較.如果是則返回True

因為代碼截圖比教麻煩.所以直接貼里面的核定代碼了.

    

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
    ....
    CRuntimeClass* pClassThis = GetRuntimeClass();  獲取自己的CRuntimeClass 成員

    ...
    return pClassThis->IsDerivedFrom(pClass); 進行判斷.
}


BOOL CRuntimeClass::IsDerivedFrom(const CRuntimeClass* pBaseClass) const
{
      其余代碼省略
    const CRuntimeClass* pClassThis = this;  得到字節的CRuntimeClass成員

    while (pClassThis != NULL)                       循環遍歷不是NULL
    {
        if (pClassThis == pBaseClass)           如果是父類則返回TRUE
            return TRUE;
        pClassThis = pClassThis->m_pBaseClass;  否則等於父類指針.繼續遍歷判斷
    }
    return FALSE;       //  沒有返回FALSE
}

 七丶編寫代碼打印輸出父類信息.以及繼承關系

    既然我們自己類的內部存儲着CRuntimeClass *的指針. 而且是一個鏈表. 它父類也有自己的. 那么完全可以進行遍歷.打印出父類的信息.

實現思路:

    1.獲取自己的CRuntimeClass *指針

    2.遍歷CRuntimeClass 成員是否為NULL

    3.不為NULL 依次打印出結構體的內容

    4. 當前的CRunTimeClass 指針修改為父類的CRuntimeClass * 指針

具體代碼實現:

  

void CMyApp::PrintParentRuntimeInfo()
{
    //1.獲取自己當前的CRuntimeClass信息. 進行遍歷.
    CRuntimeClass *pCurClass = GetRuntimeClass();
    //進行循環
    CString str;
    while (NULL != pCurClass)
    {
        str = "";
        str.Format(TEXT("類的名稱 = %s \r\n"),pCurClass->m_lpszClassName);  //類的名稱
        OutputDebugString(str);
        str = "";
        str.Format(TEXT("類的大小 = %d \r\n"),pCurClass->m_nObjectSize);
        OutputDebugString(str);
        str = "";
        str.Format(TEXT("類的編號%d \r\n"), pCurClass->m_wSchema);
        OutputDebugString(str);

        str = "";
        str.Format(TEXT("類的父類指針 %p \r\n"), pCurClass->m_pBaseClass);
        OutputDebugString(str);

        if (pCurClass->m_pBaseClass != nullptr)
        {
            
            str = "";
            str.Format(TEXT("類的父類名稱 %s \r\n"), pCurClass->m_pBaseClass->m_lpszClassName);
            OutputDebugString(str);
        }
        
        
        str = TEXT("--------------------------------\r\n");
        OutputDebugString(str);
        pCurClass = pCurClass->m_pBaseClass; //一直遍歷到頂層位置
    }
}

 

在Ininstance里面調用即可.

實現結果截圖: 使用DebugView獲取信息.

 

課堂代碼: 鏈接:https://pan.baidu.com/s/1wp6pTwsSR8QOZo0t3vNzYQ 密碼:2y2o


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM