C++實現反射(根據類名動態建立物件)
和網上大多數實現一樣,這裡也是採用工廠方法來實現物件的動態建立。大致原理為,建立一個單例工廠類,其中維護一個map(類名->物件建立函式)。建立物件時,傳入類名,然後根據此類名查詢出建立函式,最後建立物件。
採用這種方式,有一個關鍵問題,便是在工廠中註冊類名。我們的辦法是針對於每一個類(Class),定義一個註冊類(ClassReg),在註冊類(ClassReg)的建構函式中註冊此類(Class),然後再定義一個註冊類的全域性物件,在該全域性物件初始化時,便會執行註冊程式碼完成註冊。
看到到這裡懶人不樂意了,難道我們每寫一個類,還要寫一個相應的註冊類?於是有人提出了使用巨集來替換相應的程式碼,這樣便大大減少了重複程式碼量。
採用巨集當然可以,但是隻能滿足部分懶人。還有一部分人更懶,他們連使用這個巨集完成註冊都不想幹,是呀,每寫完一個類,還要在類後面使用巨集來註冊,這樣確實還是比較麻煩。而且這樣程式碼不夠美觀,也不便於維護,同時採用巨集來轉換類名,遇到名稱空間,巢狀類,會非常麻煩,我們必須在註冊的時候,把名字寫全,比如REG_CLASS(MyNameSpace::MyClass::MyStruct)。
那麼有沒有更好的方法呢?當然有,我們可以用類模板來實現這種功能。設想這種方式,若我們要定義一個類MyClass,並且想讓此類支援動態建立,那麼我們只需這樣定義即可class MyClass : public DynamicCreate<MyClass>{};。這樣是不是清晰多了?下面請看程式碼:
DynamicFactory.h檔案
#ifndef __DYNAMIC_FACTORY_H__ #define __DYNAMIC_FACTORY_H__ #ifdef __GNUC__ #include <cxxabi.h> #endif #include <assert.h> #include <string.h> #include <stdlib.h> #include <map> #include <string> #include <typeinfo> // 動態物件基類 class DynamicObject { public: DynamicObject() {} virtual ~DynamicObject() {} }; // 動態物件建立工廠 class DynamicFactory { public: typedef DynamicObject* (*CreateFunction)(); static DynamicFactory & Instance() { static DynamicFactory fac; return fac; } // 解析型別名稱(轉換為 A::B::C 的形式) static std::string ReadTypeName(const char * name) { // 這裡省略,具體程式碼在最後給出 ... } bool Regist(const char * name, CreateFunction func) { if (!func) { return false; } std::string type_name = ReadTypeName(name); return _create_function_map.insert(std::make_pair(type_name, func)).second; } DynamicObject * Create(const std::string & type_name) { if (type_name.empty()) { return NULL; } std::map<std::string, CreateFunction>::iterator it = _create_function_map.find(type_name); if (it == _create_function_map.end()) { return NULL; } return it->second(); } template<typename T> T * Create(const std::string & type_name) { DynamicObject * obj = Create(type_name); if (!obj) { return NULL; } T * real_obj = dynamic_cast<T*>(obj); if (!real_obj) { delete obj; return NULL; } return real_obj; } public: std::map<std::string, CreateFunction> _create_function_map; }; // 動態物件建立器 template<typename T> class DynamicCreate : public DynamicObject { public: static DynamicObject * CreateObject() { return new T(); } struct Registor { Registor() { if (!DynamicFactory::Instance().Regist(typeid(T).name(), CreateObject)) { assert(false); } } inline void do_nothing()const { } }; static Registor s_registor; public: DynamicCreate() { s_registor.do_nothing(); } virtual ~DynamicCreate() { s_registor.do_nothing(); } }; template <typename T> typename DynamicCreate<T>::Registor DynamicCreate<T>::s_registor; #endif
程式碼不多,就不做解釋了。
測試程式碼mian.cpp
#include <stdio.h>
#include "DynamicFactory.h"
class Test1 : public DynamicCreate<Test1>
{
public:
// 注意:使用gcc,一定要顯示申明建構函式,否則不會執行註冊程式碼
Test1() {}
};
namespace OK {
struct Test2 : public DynamicCreate<Test2>
{
Test2() {}
class Test3 : public DynamicCreate<Test3>
{
public:
Test3() {}
};
};
struct Test4 : public DynamicCreate<Test4>
{
Test4() {}
};
}
using namespace OK;
//測試程式碼
int main()
{
Test1 * p1 = DynamicFactory::Instance().Create<Test1>("Test1");
printf("Create Test1 %s\n", (p1 ? "success" : "failure"));
OK::Test2 * p2 = DynamicFactory::Instance().Create<OK::Test2>("OK::Test2");
printf("Create OK::Test2 %s\n", (p2 ? "success" : "failure"));
OK::Test2::Test3 * p3 = DynamicFactory::Instance().Create<OK::Test2::Test3>("OK::Test2::Test3");
printf("Create OK::Test2::Test3 %s\n", (p3 ? "success" : "failure"));
OK::Test4 * p4 = DynamicFactory::Instance().Create<OK::Test4>("OK::Test4");
printf("Create OK::Test4 %s\n", (p4 ? "success" : "failure"));
return 0;
}
輸出:
[[email protected] test]$ ./test
Create Test1 success
Create OK::Test2 success
Create OK::Test2::Test3 success
Create OK::Test4 success
下面給出上面省略的 ReadTypeName函式程式碼
// 解析型別名稱(轉換為 A::B::C 的形式)
// GCC 的type_info::name()輸出的名稱很猥瑣,這裡只做簡單的解析,只支援自定義的結構體(非模板),類(非模板)、列舉、聯合
static std::string ReadTypeName(const char * name)
{
#ifndef __GNUC__
const char * p = strstr(name, " ");
if (p)
{
size_t prev_len = (size_t)(p - name);
if (memcmp(name, "class", prev_len) == 0 ||
memcmp(name, "struct", prev_len) == 0 ||
memcmp(name, "enum", prev_len) == 0 ||
memcmp(name, "union", prev_len) == 0)
{
p += 1;
return std::string(p);
}
}
return std::string(name);
#else
char * real_name = abi::__cxa_demangle(name, nullptr, nullptr, nullptr);
std::string real_name_string(real_name);
free(real_name);
return real_name_string;
#endif
}