C++实现反射机制
NET下的很多技术都是基于反射机制来实现的,反射让.NET平台下的语言变得得心应手。最简单的,比如枚举类型,我们我可以很容易的获得一个枚举变量的数值以及其名称字符串。
可是,在C++中,枚举变量本质上和一个整形变量没有区别,我们很难获取一个枚举变量的名称字符串。
其实在C++中,我们可以通过宏来实现类似反射的机制。
接下来,我想总结一下如何在C++中实现一个类似于C#枚举类型的方法。
- __VA_ARGS__
- 使用__VA_ARGS__,我们可以定义带可变参数的宏,举个例子:
- #define MY_PRINTF(…) printf(__VA_ARGS__)
- 这样我们写
- MY_PRINTF("hello, %s”, "world");
- 就等价于
- printf("hello, %s”, "world");
宏的"##"符号
"##"符号的作用是在可变参数的个数为0时,消除参数前面的逗号:
#define MY_PRINTF(fs, …) printf(fs, ##__VA_ARGS__)
我们这样调用:
MY_PRINTF(“Hello, World”);
等价于
printf(“Hello, World”);
另外"##"符号还能够去掉括号,但是我现在还不是很明白,为什么能够做到这一点:
- #define ENUM_COTENTS(...) __VA_ARGS__
- #define ENUM_CONTENT_REMOVE_PARENTHESIS(a) ENUM_COTENTS##a
- #define DEFINE_ENUM(name) enum name { ENUM_CONTENT_REMOVE_PARENTHESIS(ENUM_LIST) };
- #define ENUM_LIST (Sunday=1,Monday=2)
- DEFINE_ENUM(WeekDay)
宏的"#"符号
"#"符号的作用是“字符化”代码:
#define MY_STRINGLIZED_MACRO(str) #str
int helloWorld = 0;
printf(MY_STRINGLIZED_MACRO(helloWorld)); // output: helloWorld
利用C++宏实现简单的.NET枚举类型
我做了一个简单的用例,最终示例代码如下:
- #include "DefineEnum.h"
- #define ENUM_LIST \
- ENUM_NAME(Sunday ENUM_VALUE(10)), \
- ENUM_NAME(Monday ENUM_VALUE(Sunday+1)), \
- ENUM_NAME(Tuesday ENUM_VALUE(123)), \
- ENUM_NAME(Wednesday ENUM_VALUE(10)) , \
- ENUM_NAME(Thursday ENUM_VALUE(7)), \
- ENUM_NAME(Friday ENUM_VALUE(8)), \
- ENUM_NAME(Saturday ENUM_VALUE(12))
- DEFINE_ENUM(WeekDay);
- #include "RegisterEnum.h"
- REGISTER_ENUM(WeekDay);
- int main()
- {
- printf("%s is %d.", EnumHelper<WeekDay>::ToString(Monday), Monday);
- getchar();
- return 0;
- }
- #undef ENUM_LIST
- #undef ENUM_NAME
- #define ENUM_NAME(...) __VA_ARGS__
- #undef ENUM_VALUE
- #define ENUM_VALUE(val) = val
- #define ENUM_COTENTS(...) __VA_ARGS__
- #define DEFINE_ENUM(name) enum name { ENUM_COTENTS(ENUM_LIST) };
- #include "ReflectEnum.h"
- #undef ENUM_VALUE
- #define ENUM_VALUE(val)
- #define REGISTER_ENUM(name) REFLECT_ENUM(name, ENUM_LIST )
- #ifndef REFLECT_ENUM_INCLUDE_GUARD
- #include <string>
- #include <cstring>
- #include <stdexcept> // for runtime_error
- #endif
- template <typename Enum_T> class EnumHelper
- {
- public:
- static const char * ToString(Enum_T e)
- {
- for(int i = 0; i < _countof(EnumHelper<Enum_T>::s_allEnums); i++)
- {
- if( s_allEnums[i] == e)
- return s_allEnumNames[i];
- }
- return NULL;
- }
- private:
- static const char * s_typeName;
- static Enum_T s_allEnums[];
- static char s_singleEnumStr[];
- static const char * s_allEnumNames[];
- static void SplitEnumDefString()
- {
- char * p = s_singleEnumStr;
- while( isspace(*p) ) p++;
- for(int i = 0; i < _countof(EnumHelper<Enum_T>::s_allEnums); i++)
- {
- s_allEnumNames[i] = p;
- while( *p == '_' || isdigit(*p) || isalpha(*p) ) p++;
- bool meet_comma = ( *p == ',' );
- *p++ = '\0';
- if( !meet_comma )
- {
- while( *p && *p != ',') p++;
- if( *p ) p++;
- }
- while( *p && isspace(*p) ) p++;
- }
- }
- };
- #define TO_ENUM_ITEM(...) __VA_ARGS__
- #define STRINGIZE(...) #__VA_ARGS__
- #define REFLECT_ENUM(enum_type_name, enum_list) \
- template<> enum_type_name EnumHelper<enum_type_name>::s_allEnums[] = \
- { \
- TO_ENUM_ITEM(enum_list) \
- }; \
- template<> const char* EnumHelper<enum_type_name>::s_allEnumNames[_countof(EnumHelper<enum_type_name>::s_allEnums)]; \
- template<> char EnumHelper<enum_type_name>::s_singleEnumStr[] = STRINGIZE(enum_list) ; \
- template<> const char * EnumHelper<enum_type_name>::s_typeName = (EnumHelper<enum_type_name>::SplitEnumDefString(), #enum_type_name);
如果你问一个IT人士“C++如何实现类似Java的反射?”,结果会怎样呢?~!@#¥%……&*,估计大部分人都会要稍微思考了一下,或者直接说“C++根本就不支持反射的呀!”。
是的,C++语言本身是不支持反射的,但实际应用中总是会有将对象序列化的需求,总不可能C++不支持,我们就不用C++了,既然发明C++的大师们没有考虑这个,那我们只有自己动手了,毛主席说过“自己动手,丰衣足食”!
天生限制
C++语言本身不支持反射机制,但C++对象总是要序列化的,序列化就是存储到磁盘上,将对象变成一定格式的二进制编码,然后要用的时候再将保存在磁盘上的二进制编码转化成一个内存中的对象,这个过程中总是需要有一个指示来告诉编译器要生成什么样的对象,最简单的方式当然就是类名了,例如:将一个ClassXXX对象存储到磁盘上,再从磁盘读取的时候让编译器根据“ClassXXX”名称来new一个对象。
但是问题出现了,C++语言本身不支持反射,也就是说不能通过如下方式生成一个对象:
ClassXXX object = new “ClassXXX”;
工厂方法
当然,这样的方法不行,那我们只有另辟蹊径。最简单的就是工厂方法了:
ClassXXX* object = FactoryCreate(“ClassXXX”);
至于FactoryCreate的设计就很简单了,if的集合就可以了:
if(name = “ClassXXX”)
return new ClassXXX;
if(name = “ClassYYY”)
return new ClassYYY;
看起来不错,来个类名就可以生成对应的对象,功能上解决了根据类名生成对象的问题。
假如以上所有的代码都有你一个人编写,那当然问题不大,但是假如有一天你的公司扩大了,这部分代码由两个不同的组A和B来维护,啊哈,问题来了,A组每添加或者修改一个类,都要通知B组更新FactoryCreate函数,也就是说A组的任何关于类的修改,都需要B组来修改,但实际上B的修改不产生任何价值,而且不胜其烦,永无止尽!!如果哪天来了一个新员工,由于对这个规定还不清楚,忘记了通知,那就完了:编译通不过!
一个公司内都会产生如此多的问题,更何况微软这样的大公司是面对全球的各种各样的客户,如果微软把这部分做进框架代码中,呵呵,那微软所有的人不用干其他事情了,每天处理来自全球的要求修改FactoryCreate函数的邮件和电话就够他们忙的了:)
回调工厂
既然此路不好走,那么我们再考虑其它方法吧,一个可选的方法是将FactoryCreate做成回调函数,框架提供注册接口RegisterFactoryCreate,框架函数如此实现:
typedef CObject* (*FactoryCreate_PTR)(String name);
RegisterFactoryCreate(FactoryCreate_PTR fc_ptr);
应用代码如此实现:
CObject* MyFactoryCreate(String name);
RegisterFactoryCreate(MyFactoryCreate);
到这里一个框架和应用分离的反射机制基本实现了,你是否长吁一口气,然后准备泡杯咖啡,稍微放松一下呢?确实可以稍微休息一下了,毕竟我们完成了一件非常了不起的事情,让C++实现了反射。
但你只悠闲了一两天,麻烦事就来了。员工张三跑来向你抱怨“老大,李四注册的反射函数把我的覆盖了”!哦,你仔细一看,My god,这个注册函数只能注册一个反射函数,后注册的就把前面的覆盖了!
怎么办?总不可能又要求所有的类的反射函数都在一个工厂里实现吧,那这样就又回到了工厂方法中描述的时代了。
当然,聪明的你估计很快就能想出问题的解决方法,将RegisterFactoryCreate函数稍加修改就能满足要求了,新的实现如下:
RegisterFactoryCreate(FactoryCreate_PTR fc_ptr,String className)
然后要求每个类都单独写自己的FactoryCreate_PTR函数,类似如下方式:
static CObject* ClassXXX::CreateClassXXX (){
return new ClassXXX;
};
static CObject* ClassYYY::CreateClassYYY(){
return new ClassYYY;
};
到此为此终于大功告成,通过我们的智慧实现了C++的反射功能!一股自豪感油然升起:)
最后的杀手锏:宏
当你为自己的聪明才智而骄傲的时候,那边却有几个开发的兄弟在发出抱怨“唉,这么多相似的函数,看着都眼花,每个类都要写,烦死了”。
或者有一天,你要在每个类的CreateClass函数中增加一个其它功能(例如日志),那么开发的兄弟真的是要烦“死了”!!!
其实仔细一看,包括函数申明、函数定义、函数注册,每个类的代码除了类名外其它都是一模一样的,有没有简单的方法呢?
肯定是有的,这个方法就是宏了,按照如下方法定义宏:
- #define DECLARE_CLASS_CREATE(class_name) \
- static CObject* CreateClass## class_name ();
- #define IMPL_CLASS_CREATE(class_name) \
- static CObject* CreateClass## class_name (){ \
- return new class_name; \
- };
- #define REG_CLASS_CREATE(class_name) \
- RegisterFactoryCreate(class_name::CreateClass## class_name, #class_name);
注:##是连接符,将两个字符串连接起来,#是将class_name作为字符串处理。
大家可以比较一下,用了宏和不用宏是不是代码感觉完全不一样呢?而且那天需要增加一个简单的功能,只需要改宏定义就ok了,不要全文搜索所有相关函数,然后一个一个的重复添加。
到这里才真正是大功告成!!
后记
某天分析Spring的IOC时,看到Digester最后利用的实际上是Java的反射机制来根据XML文件定义生成Java对象,突发奇想:如果是C++该怎么办?
于是自己就开始分析起来,分析了一段时间突然想起微软的MFC不正是要支持C++对象序列化的吗?
赶紧打开深入浅出MFC,重新将这部分研究了一下。看到微软的天才们在MFC中用宏来实现RTTI、Dynamic Create、Seralize功能时,我反过来思考“如果是我,我会如何设计?”、“为什么会这么设计”?然后一一分析这些各种可能的实现方式,一步一步的推导,最后发现竟然自然而然的就推出了MFC的这种实现方式!
当然,MFC的实现代码和我给出的代码不一样(注册方式不一样),但设计思想是一样的,各位看官可以自行稍加分析就明白了。
MFC的详细实现可以参考侯捷的《深入浅出MFC》。
转载自:http://blog.csdn.net/wangweitingaabbcc/article/details/7916963
C++实现反射机制的更多相关文章
- Java学习之反射机制及应用场景
前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...
- 第28章 java反射机制
java反射机制 1.类加载机制 1.1.jvm和类 运行Java程序:java 带有main方法的类名 之后java会启动jvm,并加载字节码(字节码就是一个类在内存空间的状态) 当调用java命令 ...
- NPOI操作EXCEL(四)——反射机制批量导出excel文件
前面我们已经实现了反射机制进行excel表格数据的解析,既然有上传就得有下载,我们再来写一个通用的导出方法,利用反射机制实现对系统所有数据列表的筛选结果导出excel功能. 我们来构想一下这样一个画面 ...
- Java反射机制
Java反射机制 一:什么事反射机制 简单地说,就是程序运行时能够通过反射的到类的所有信息,只需要获得类名,方法名,属性名. 二:为什么要用反射: 静态编译:在编译时确定类型,绑定对象,即通过 ...
- java基础知识(十一)java反射机制(上)
java.lang.Class类详解 java Class类详解 一.class类 Class类是java语言定义的特定类的实现,在java中每个类都有一个相应的Class对象,以便java程序运行时 ...
- java基础知识(十一)java反射机制(下)
1.什么是反射机制? java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象都能够调用他的属性和方法,这种动态获取属性和方法的功能称为java的反射机制. ...
- c#反射机制
一:反射的定义 审查元数据并收集关于它的类型信息的能力.元数据(编译以后的最基本数据单元)就是一大堆的表,当编译程序集或者模块时,编译器会创建一个类定义表,一个字段定义表,和一个方法定义表等. Sys ...
- java反射学习之一反射机制概述
一.反射机制背景概述 1.反射(reflection)是java被视为动态语言的一个关键性质 2.反射机制指的是程序在运行时能获取任何类的内部所有信息 二.反射机制实现功能概述 1.只要给定类的全名, ...
- Java中的反射机制
Java反射机制 反射机制定义 反射机制是Java语言中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许其对内部成员进行操作.由于反射机制能够实现在运行时对类进行装载,因此能够增加程序的 ...
- C#反射机制 (转载)
转载:原文出处 http://www.cnblogs.com/binfire/archive/2013/01/17/2864887.html 一:反射的定义 审查元数据并收集关于它的类型信息 ...
随机推荐
- PL/SQL之--变量
一.PL/SQL 简介 PL/SQL也是一种程序语言,叫做过程化SQL语言(Procedural Language/SQL).PL/SQL是oracle对sql语句的一种扩展,在普通SQL语句的使用上 ...
- linux64位android项目R文件无法生成以及Cannot run program adb
1.本机kali2.0 64位,kali基于Debian. 2.android adb是32位,64位linux要安装32位依赖库,注意ia32-lib被lib32z1替代. 3.执行命令 sudo ...
- R语言与数据分析
K最近邻(k-Nearest Neighbor,KNN)分类算法 R语言实现包:R语言中有kknn package实现了weighted k-nearest neighbor. 决策树: R语言实现决 ...
- php 利用activeMq+stomp实现消息队列
php 利用activeMq+stomp实现消息队列 一.activeMq概述 ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线.ActiveMQ 是一个完全支持JMS1.1和J ...
- memcpy函数
实现1:<高质量c++,c编程指南> void *mymemcpy(void *dst,const void *src,size_t num) { assert((dst!=NULL)&a ...
- Hive UDF’S addMonths
our project use hive 0.10 , and in the hiveql , we need use addMonths function builtin in hive-0.11. ...
- POJ 1151 Atlantis 线段树求矩形面积并 方法详解
第一次做线段树扫描法的题,网搜各种讲解,发现大多数都讲得太过简洁,不是太容易理解.所以自己打算写一个详细的.看完必会o(∩_∩)o 顾名思义,扫描法就是用一根想象中的线扫过所有矩形,在写代码的过程中, ...
- 第11章 Windows线程池(3)_私有的线程池
11.3 私有的线程池 11.3.1 创建和销毁私有的线程池 (1)进程默认线程池 当调用CreateThreadpoolwork.CreateThreadpoolTimer.CreateThread ...
- Linux搭建python环境
环境: CentOS 6.4 前言:CentOS 6.4系统自带的有python2.6.6版本 一.下载文件 python官网:https://www.python.org/downloads/ 版本 ...
- Volley(四)—— ImageLoader & NetworkImageView
Volley(四)—— ImageLoader & NetworkImageView ImageLoader是一个加载网络图片的封装类,其内部还是由ImageRequest来实现的.但因为源码 ...