一 背景
最近看到一篇文章《关于C语言,我喜欢和讨厌的十件事》http://geek.csdn.net/news/detail/3399 里面说到C++不支持反射的问题,这是个事实,起码不是原生支持,但我认为是可以模拟的。就像C
语言模拟C++的继承,虚函数等一样。
二 什么是反射
反射(reflection)一般来说是 .net 或者JAVA 中的概念,这个问题我就不拷贝了,传送门:http://www.cnblogs.com/laskosoft/articles/1359784.html
初步的理解,说白点就是通过字符串实例化对象,调用对象的接口(有误请猛拍~)
三 需要什么
通过以上的了解,我们需要的大概是这么个东西:
CReflectable* obj = SomeThing->GetObjectByName("CTest"); if(!obj){ return; } // 调用 1,是 Hello 的参数 int ret = obj->CallMemFun("Hello",1);
看起来很简单的样子。这个离完整的反射概念可能还有距离,但,就从这里出发。
四 构思
通过以上表明,其实需要的就是通过字符串实例化对象,通过字符串调用成员函数。在这里,字符串和类名应该是一致的。通过字符串实例化对象见的最多的是类工厂:
CObject* CreateInstance(std::string name) { if( name == "CTest" ) return new CTest(); else if( name == "CFriend" ) return new CFriend(); else return 0; }
一眼望去便知有很大的不足,其实也就是无法满足要求,因为你无法通过手写 if else if 遍历所有的字符串可能性,你也无法定义那么多对象。这种做法压根就是行不通的。
所以创建根据字符串创建对象这个事情应该更“被动”,当然是回调函数。
// 定义动态创建的回调 typedef CReflectable* (*CreateFun)(); // 注册回调函数 void Register(std::string name,CreateFun fun){ map.insert(name,fun); } // 动态创建,取代之前的 if else if CObject* CreateInstance(std::string name){ map::iterator it = map.find(name); if(it != map.end()) return it->second(); return 0; }
这里有一个假定:要动态创建的类必须继承自 CReflectable。
这样,根据字符串创建对象的基础设施已经基本就位。接下来看看怎么根据传入的字符串调用实例化对象的成员函数。
一种想法的虚函数,但仍然和 if else if 那个有类似的问题,就是都需要提前预定义,而“根据字符串”这个事情是未知的。既然我们可以通过注册回调的方式创建对象,也应该可以通过类似的方法“创建”成员调用。
五 类对象的内存模型和成员函数
这个话题其实是个很经典的话题,有很多种情形需要了解对象的内存布局。而一般来说,在我们没必要知道那么细的情况下,我们必须知道几个基本点:
1,非虚函数不占用对象存储区 。 比如
class A { int m_count; public: int GetCount(){return m_count;} void SetCount(int c){m_count=c;} } // A a; int size = sizeof(a);
size的值是 4.这就表明 GetCount() 和 SetCount() 不占类对象的空间。[有的朋友可能会问:为什么这个4就一定是 m_count呢?可以更进一步调试的。]
2,虚函数表指针 占4个字节,一般在对象开始部分(单继承)。
3,空类占 1 字节。
4,继承发生时,转换会发生 slicing。注意 new 时的真正对象和强制转换时的内存切割。
我们只关注成员函数的一些特性。我们都知道2点:1,调用成员函数必须通过对象;2,调用成员函数时,隐含传递了 this 指针。其实这两点是相辅相成,互为条件的。既然需要传 this 指针,那么我们可以断定:成员函数的地址在内存中是唯一的。
针对我们的问题,我们需要注册成员函数的字符串表述,函数地址,和调用这个函数的对象。这样,我们可以在实例化期间根据传入的字符串检索出对应的对象和成员函数,通过对象调用成员函数完成实现。
//类 CDerive 的成员函数 int Hello() 的指针 typedef int (CDerive::*FunHello)(); //通过指针调用 Hello() CDerive d; FunHello h = &CDerive::Hello; (d.*h)();
但我们无法定义这样的接口,因为要动态生成的类对象CDerive 在你现在的设计阶段是不存在的,他也可能是 CDeriveFF 或别的什么名称,你无法显式的给出它的声明继而注册入列表中。在设计时,我们可用的资源只是基类的 this 指针,也就是前面回调工厂产生的指向子类对象的指针。任何有关与未来类有关的声明,在这里不可探测。甚至我们无法声明子类的成员函数定义。
所以,注册的过程必须在子类中进行,而且应该为虚函数的方式。
同时,前面说的注册时需要的“调用这个成员函数的对象” 并不是必须的,因为回调工厂产生了一个指向子类的指针,只是以基类指针的形式返回,我们可以安全的把它再次转换回去而不会发生错误的 slicing 。
所以在基类中声明
class CReflectable{ ... virtual void Register(); map<std::string,long> m_map //long 存储成员函数地址 }
要动态创建的类实现
class CTest:public CReflectable { void Register() { m_map.insert("Add",(long)&CTest::Add); // } }
调用部分就很清晰了:
// 父类 class CReflectable{ virtual int CallMember(std::string name); } // 子类 class CTest{ int CallMember(std::string name) { map::iterator it = m_map.find(name); if(it != m_map.end()){ CTest::Add add = it->second; (this->*add)(); }else return ERR_CODE; } }
如果加入宏,则会显得更完美一点
六 例子
看看我的一个测试子类的完整实现:
class CTest:public CReflectable{ DECLARE_REFLECT(CTest) EXPORT_MEMBER_FUN("Hello",&CTest::Hello); EXPORT_MEMBER_FUN("SetStr",&CTest::SetStr); EXPORT_MEMBER_FUN("SetInfo",&CTest::SetInfo); DECLARE_REFLECT_END(); int Hello(int param){ TCHAR tmp[8]={0}; wsprintf (tmp,L"%d",param); MessageBox(0,tmp,L"",MB_OK); return 0; } int SetStr(int addr){ char* str = (char*)addr; strcpy_s(m_str,64,str); return 0; } int SetInfo(int astudent){ student_t* stu =(student_t*)astudent; if(stu) memcpy(&m_stu,stu,sizeof(student_t)); return 0; } char m_str[64]; student_t m_stu; };// CTest
调用部分:
int main() { //实例化 CReflectable* test = CReflectorFactory::GetInstance()->GetObjectByName("CTest"); if(!test){ return; } // 自注册 test->RegisterMemberFun(); // 所支持函数字符串名称列表 std::vector<std::string> funs; test->GetSupport(funs); // 调用 int ret = test->CallMemFun("Hello",1); char* str = "Hello World"; ret = test->CallMemFun("SetStr",(int)str); student_t s; s.id = 1; strcpy_s(s.name,32,"yyk882002@gmail"); ret = test->CallMemFun("SetInfo",(int)&s); }
暂时这样。稍后代码上传至那个地方,准备收1分,算大家帮我个忙。