1. 程式人生 > >C++實現反射(二)

C++實現反射(二)

找了一些資料,參考了 C++反射——開源中國 這篇,做了一些修改和簡化,成為了 Version3.

思路其實並不複雜,可以進行反推:
1. 反射是根據類名動態生成類,如果我們有一個全域性的對映關係,可以從類名得到類的相關資訊 ClassInfo,包括類的建構函式,那麼我們便能實現這一點。所以我們需要維護一個 map<std::string, ClassInfo> 這樣的結構作為全域性對映,並提供 Register 介面作為 map 的註冊操作,只要再定義一個 CreateObject(const std::string class_name),每次在 map 中檢索到對應的 ClassInfo,然後建立並返回物件,這樣就有了反射的基礎。
2. 那麼 CreateObject

函式返回的型別是什麼呢?因為反射得到的類各不相同,所以我們需要一個 Object 類作為所有反射類的基類,這樣只要返回一個 Object* 指標就好了。
3. 而 ClassInfo 是作為儲存類資訊的結構,所以我們需要把類名和建立物件的函式指標儲存下來。除此之外,還應提供一個 CreateObject 的成員函式供呼叫生成類。
4. 最後,ClassInfo 在哪裡建立和儲存呢?顧名思義,每個 ClassInfo 物件應該是每個類唯一的,所以我們可以每個類定義一個 static 的 ClassInfo 物件。還有,每個類要提供構造自身的介面,這樣才可以把介面儲存到 ClassInfo 中,供 CreateObject
呼叫。因為這兩點每個反射類都是相似的,可以藉助巨集來簡化。

這樣分析完是不是挺簡單的?當然更大的可能是你被我繞暈了。可以看看下面的程式碼,回過頭再看一遍分析,應該就清楚了。:)

// base.h
#ifndef __BASE_H__
#define __BASE_H__
#include <string>
#include <map>


class Object;
class ClassInfo;

typedef Object* (*ObjectConstructorFn)(void);
bool Register(ClassInfo* ci);

class
ClassInfo { public: ClassInfo(const std::string class_name, ObjectConstructorFn object_constructor): class_name_(class_name), object_constructor_(object_constructor) { Register(this); // 構造的時候就進行註冊 } // 根據儲存下來的函式指標構造物件 Object* CreateObject() const { return object_constructor_ ? (*object_constructor_)() : nullptr; } ~ClassInfo() {} public: std::string class_name_; ObjectConstructorFn object_constructor_; }; // 維護全域性的對映關係 static std::map<std::string, ClassInfo*> *class_info_map = nullptr; // 每個反射類的都需要一個 ClassInfo 和 CreateObject #define DECLARE_CLASS(name) \ protected: \ static ClassInfo class_info_; \ public: \ static Object* CreateObject(); // class_info 用類名和類的 CreateObject 函式初始化 // 在每個反射類的 cpp 檔案中使用該巨集 #define IMPLEMENT_CLASS(name) \ ClassInfo name::class_info_(#name, (ObjectConstructorFn) name::CreateObject);\ Object* name::CreateObject() { \ return new name; \ } // 所有反射類的基類 // 用來傳遞反射物件的指標 class Object { DECLARE_CLASS(Object); public: Object() {} virtual ~Object() {} // 提供全域性可使用的 CreateObject 函式,根據類名生成物件 static Object* CreateObject(std::string name); }; IMPLEMENT_CLASS(Object) Object* Object::CreateObject(std::string name) { std::map<std::string, ClassInfo*>::const_iterator iter = class_info_map->find(name); if(class_info_map->end() != iter) { return iter->second->CreateObject(); } return nullptr; } // map 的註冊介面 bool Register(ClassInfo* ci) { if (!class_info_map) { class_info_map = new std::map<std::string, ClassInfo*>(); } if (ci) { if (class_info_map->find(ci->class_name_) == class_info_map->end()) { class_info_map->insert( std::map<std::string, ClassInfo*>::value_type(ci->class_name_, ci)); } } } #endif

使用的時候包含該標頭檔案,反射類(例如 A)繼承 Object,並在類中加入巨集 DECLARE_CLASS(A),在類的實現檔案中加入巨集 IMPLEMENT_CLASS(A),最後建立物件時使用 Object::CreateObject("A") 就可以了。

// base_test.cpp
#include<iostream>
#include<cstring>
#include "base.h"

using namespace std;

class A : public Object
{
    DECLARE_CLASS(A)
  public :
    A(){ std::cout << "A constructor!" << std::endl; }
    virtual test() { std::cout << "I'm class A!" << std::endl; }
    ~A() { std::cout << "A destructor!" << std::endl; }
};
IMPLEMENT_CLASS(A)

class B : public Object
{
    DECLARE_CLASS(B)
  public :
    B(){ std::cout << "B constructor!" << std::endl; }
    virtual test() { std::cout << "I'm class B!" << std::endl; }
    ~B() { std::cout << "B destructor!" << std::endl; }
};
IMPLEMENT_CLASS(B)

int main()
{
    Object* p = Object::CreateObject("A");
    delete p;
    p = Object::CreateObject("B");
    delete p;
    p = Object::CreateObject("A");
    delete p;
    return 0;
}

到這一步我們就真正實現反射了,是不是其實並不難?但我還是覺得不夠好:
1. 每個反射類都要繼承於 Object,看起來總是有點奇怪,使用的時候要求使用方知道這個用 Object 指標來儲存,不甚明朗。
2. 對於我來說,我只想實現根據類名建立物件,所以程式碼其實可以更簡化一點,map 裡直接儲存類名和函式指標的對映就好了,這樣整體會更簡化些。

下一篇我們解決這兩個問題,實現 Version 4.