C++能不能支持Java和ObjC的反射?
要回答这个问题,首先我们要清楚什么是反射。什么是反射?
教科书的解释我就不说了,(^o^)其实我也记不得。实际开发应用的反射就是在没有某个类型的头文件或者类结构定义的情况下,存取这个类型的对象的成员字段的值,调用这个对象的成员函数(方法)。
比如我有定义了一个类型 Class A,里面有 a,b,c三个字段,有fun()函数。现在我手里只有一个 void* pA,注意它的类型只是一个void指针,我手里也没有Class的头文件,我要怎么样得到,a,b,c的值呢?怎么调用fun函数呢?还有一种需求就是通过字符串执行代码。比如已经拿到了一A的对象pA了,还拿到了一个字符串 "pA->fun()",现在怎么样才能将这个字符串代表的代码,执行了呢?(后面这种需求最典型的需求就是表达式引擎)。以上说的就是反射的用处和好处,很多实际需求中使用起来是非常非常方便的,可以在很多场合节省很多时间,少写很多代码。
众所周知,Java和ObjC支持反射,当然还有其他语言也支持,本人知识水平有限,不能完全列举还请见谅。
如果要实现反射,就需要实现如下几个功能:
拿到一个对象指针,哪怕它是void,也能通过一些API,得到它的真实类类型名称,比如java可以得到它的实际类型的包路径+类名
拿到这个对象指针,还能得到它所有的成员变量,成员函数的名称,类型(字段),参数和返回类型(函数),以字符串或者其他封装类型返回,核心还是字符串。
拿到这个对象指针,可以通过字符串,执行某个函数,可以通过字符串,取得某个成员的值。
Java和ObjC都能支持反射,还有一个前提是因为他们都统一基类,比如java里叫 java.lang.Object 。ObjC里面是 NSObject。有了统一基类,才能够预先定义一套反射的机制和API,任何继承这个基类的实现类,都附带赠送了早已实现的反射功能。
而C++没有标准基类,各种框架,各种平台有各种各样的框架,类库。
假设我们自己在实际开发的系统里面,定义一个统一基类,是不是能在C++里面,实现反射功能,哪怕是别扭的实现也好啊,这样也可以减少很多苦逼C++开发人员的工作量的呀,多好的事啊。试试吧
首先我们假设应用程序里面的所有类,都有一个公共基类。
其次C++里面的成员变量,成员函数,对象,所有一切,都是指针定位。所以我们先要解决,通过“一个对象指针 + 一个函数指针调用函数的问题”。
完整演示程序已经写好了
#include "stdafx.h" class CXObject { public: int doSum(int a, int b); }; typedef int (CXObject::*pf_doSum)(int, int); int CXObject::doSum(int a, int b) { return a + b ; } void dosome(CXObject* pObj, pf_doSum func) { int result = (pObj->*func)(100, 23); printf("result=%d", result); } int _tmain(int argc, _TCHAR* argv[]) { CXObject obj ; dosome(&obj, &CXObject::doSum); getchar(); return 0; }
可以看到,可以通过一个对象指针,加一个函数指针 调用函数。这是C++里面也算是很常用的技巧了。MFC里面系统生成的代码很多都是用这种方式。
我们的需求是通过字符串,调用函数,所以就需要建立一个 函数名称 到 函数指针的映射关系。然后就可以字符串查找 函数指针,函数指针调用函数了。
请看完整代码:
// test01.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <vector> #include <string> class CXObject ; typedef void (CXObject::*void_func_void)(void); class CXObject { public: //取得类的所有函数的名称 virtual void getMethodNames(std::vector<std::string>& names); //根据名称,取得函数指针 virtual void_func_void getFuncByName(std::string& name); }; void CXObject::getMethodNames(std::vector<std::string>& names){} void_func_void CXObject::getFuncByName(std::string& name){return NULL;} class CXMyImpl : public CXObject { public: //需要实现的函数 //取得类的所有函数的名称 virtual void getMethodNames(std::vector<std::string>& names); //根据名称,取得函数指针 virtual void_func_void getFuncByName(std::string& name); public://自己定义的业务函数。 void doPrint1(); void doPrint2(); }; void CXMyImpl::getMethodNames(std::vector<std::string>& names) { names.push_back("doPrint1"); names.push_back("doPrint2"); } void_func_void CXMyImpl::getFuncByName(std::string& name) { void_func_void pFun = NULL ; if(name.compare("doPrint1") == 0) pFun = static_cast<void_func_void>(&CXMyImpl::doPrint1) ; if(name.compare("doPrint2") == 0) pFun = static_cast<void_func_void>(&CXMyImpl::doPrint2) ; return pFun ; } void CXMyImpl::doPrint1() { printf("CXMyImpl::doPrint1 be called!!\r\n"); } void CXMyImpl::doPrint2() { printf("CXMyImpl::doPrint2 be called!!\r\n"); } int _tmain(int argc, _TCHAR* argv[]) { CXObject* obj = new CXMyImpl(); //后面的代码里,并没有CXMyImpl,CXObject里面没有定义doPrint1,doPrint2. //通过字符串的doPrint1执行doPrint1函数。 std::vector<std::string> names ; obj->getMethodNames(names); for(int idx=0; idx<names.size(); idx++) { void_func_void pfun = obj->getFuncByName(names[idx]); (obj->*pfun)(); } getchar(); return 0; }
运行结果如下:
从上面的例子可以看到,其实是可以模拟出反射的效果的。
如果要C++完全的实现方便实用的反射机制,需要以下几点条件:
1、统一的基类
2、编译器要帮忙
对于第二条条件,可以这样理解:
在上例子中
void CXMyImpl::getMethodNames(std::vector<std::string>& names) { names.push_back("doPrint1"); names.push_back("doPrint2"); } void_func_void CXMyImpl::getFuncByName(std::string& name) { void_func_void pFun = NULL ; if(name.compare("doPrint1") == 0) pFun = static_cast<void_func_void>(&CXMyImpl::doPrint1) ; if(name.compare("doPrint2") == 0) pFun = static_cast<void_func_void>(&CXMyImpl::doPrint2) ; return pFun ; }
这两个函数是基类里面定义好了的,算是反射相关的API,但是如果我每写一个类都要自己实现这两个函数,那岂不是要累死。
但是反过来看,这两个函数的功能非常简单,代码非常有规律。那就是记录当前的类有那些函数,把名字和函数指针对应起来,这个过程完全可以让编译器在编译的时候自动为我加上。
另一方面,由于为了方便演示,这里面用到的业务函数都是 void f(void)类型的,没有参数和返回值,这样显然是不合理的。但是并不是不能实现,只不过那样更复杂,绕的圈子更多。
在没有这样的编译器出现,在没有这样的统一基类的类库的情况下,C++的反射,只是停留在兴趣调研的阶段,目前没看到什么实际价值,个人感觉的哈。