C++實現反射(二)
阿新 • • 發佈:2018-11-01
找了一些資料,參考了 C++反射——開源中國 這篇,做了一些修改和簡化,成為了 Version3.
思路其實並不複雜,可以進行反推:
1. 反射是根據類名動態生成類,如果我們有一個全域性的對映關係,可以從類名得到類的相關資訊 ClassInfo,包括類的建構函式,那麼我們便能實現這一點。所以我們需要維護一個 map<std::string, ClassInfo>
這樣的結構作為全域性對映,並提供 Register 介面作為 map 的註冊操作,只要再定義一個 CreateObject(const std::string class_name)
,每次在 map 中檢索到對應的 ClassInfo,然後建立並返回物件,這樣就有了反射的基礎。
2. 那麼 CreateObject
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.