C++实现反射机制

NET下的很多技术都是基于反射机制来实现的,反射让.NET平台下的语言变得得心应手。最简单的,比如枚举类型,我们我可以很容易的获得一个枚举变量的数值以及其名称字符串。

可是,在C++中,枚举变量本质上和一个整形变量没有区别,我们很难获取一个枚举变量的名称字符串。

其实在C++中,我们可以通过宏来实现类似反射的机制。

接下来,我想总结一下如何在C++中实现一个类似于C#枚举类型的方法。

[cpp] view plaincopy

  1. __VA_ARGS__
  2. 使用__VA_ARGS__,我们可以定义带可变参数的宏,举个例子:
  3. #define MY_PRINTF(…) printf(__VA_ARGS__)
  4. 这样我们写
  5. MY_PRINTF("hello, %s”, "world");
  6. 就等价于
  7. printf("hello, %s”, "world");

宏的"##"符号

"##"符号的作用是在可变参数的个数为0时,消除参数前面的逗号:

#define MY_PRINTF(fs, …) printf(fs, ##__VA_ARGS__)

我们这样调用:

MY_PRINTF(“Hello, World”);

等价于

printf(“Hello, World”);

另外"##"符号还能够去掉括号,但是我现在还不是很明白,为什么能够做到这一点:

[cpp] view plaincopy

  1. #define ENUM_COTENTS(...) __VA_ARGS__
  2. #define ENUM_CONTENT_REMOVE_PARENTHESIS(a) ENUM_COTENTS##a
  3. #define DEFINE_ENUM(name) enum name { ENUM_CONTENT_REMOVE_PARENTHESIS(ENUM_LIST) };
  4. #define ENUM_LIST (Sunday=1,Monday=2)
  5. DEFINE_ENUM(WeekDay)

宏的"#"符号

"#"符号的作用是“字符化”代码:

#define MY_STRINGLIZED_MACRO(str) #str
int helloWorld = 0;
printf(MY_STRINGLIZED_MACRO(helloWorld)); // output: helloWorld

利用C++宏实现简单的.NET枚举类型

我做了一个简单的用例,最终示例代码如下:

[cpp] view plaincopy

  1. #include "DefineEnum.h"
  2. #define ENUM_LIST                                   \
  3. ENUM_NAME(Sunday     ENUM_VALUE(10)),       \
  4. ENUM_NAME(Monday     ENUM_VALUE(Sunday+1)),     \
  5. ENUM_NAME(Tuesday    ENUM_VALUE(123)),      \
  6. ENUM_NAME(Wednesday  ENUM_VALUE(10)) ,      \
  7. ENUM_NAME(Thursday   ENUM_VALUE(7)),        \
  8. ENUM_NAME(Friday     ENUM_VALUE(8)),        \
  9. ENUM_NAME(Saturday   ENUM_VALUE(12))
  10. DEFINE_ENUM(WeekDay);
  11. #include "RegisterEnum.h"
  12. REGISTER_ENUM(WeekDay);
  13. int main()
  14. {
  15. printf("%s is %d.", EnumHelper<WeekDay>::ToString(Monday), Monday);
  16. getchar();
  17. return 0;
  18. }

DefineEnum.h

[cpp] view plaincopy

  1. #undef ENUM_LIST
  2. #undef ENUM_NAME
  3. #define ENUM_NAME(...)  __VA_ARGS__
  4. #undef ENUM_VALUE
  5. #define ENUM_VALUE(val) = val
  6. #define ENUM_COTENTS(...)  __VA_ARGS__
  7. #define DEFINE_ENUM(name)  enum name { ENUM_COTENTS(ENUM_LIST) };

RegisterEnum.h

[cpp] view plaincopy

  1. #include "ReflectEnum.h"
  2. #undef ENUM_VALUE
  3. #define ENUM_VALUE(val)
  4. #define REGISTER_ENUM(name)  REFLECT_ENUM(name, ENUM_LIST )

ReflectEnum.h

[cpp] view plaincopy

  1. #ifndef REFLECT_ENUM_INCLUDE_GUARD
  2. #include <string>
  3. #include <cstring>
  4. #include <stdexcept> // for runtime_error
  5. #endif
  6. template <typename Enum_T> class EnumHelper
  7. {
  8. public:
  9. static const char * ToString(Enum_T e)
  10. {
  11. for(int i = 0; i < _countof(EnumHelper<Enum_T>::s_allEnums); i++)
  12. {
  13. if( s_allEnums[i] == e)
  14. return s_allEnumNames[i];
  15. }
  16. return NULL;
  17. }
  18. private:
  19. static const char * s_typeName;
  20. static Enum_T s_allEnums[];
  21. static char s_singleEnumStr[];
  22. static const char * s_allEnumNames[];
  23. static void SplitEnumDefString()
  24. {
  25. char * p = s_singleEnumStr;
  26. while( isspace(*p) ) p++;
  27. for(int i = 0; i < _countof(EnumHelper<Enum_T>::s_allEnums); i++)
  28. {
  29. s_allEnumNames[i] = p;
  30. while( *p == ‘_‘ || isdigit(*p) || isalpha(*p) ) p++;
  31. bool meet_comma = ( *p == ‘,‘ );
  32. *p++ = ‘\0‘;
  33. if( !meet_comma )
  34. {
  35. while( *p && *p != ‘,‘) p++;
  36. if( *p ) p++;
  37. }
  38. while( *p && isspace(*p) ) p++;
  39. }
  40. }
  41. };
  42. #define TO_ENUM_ITEM(...)  __VA_ARGS__
  43. #define STRINGIZE(...)  #__VA_ARGS__
  44. #define REFLECT_ENUM(enum_type_name, enum_list)                                                                         \
  45. template<> enum_type_name EnumHelper<enum_type_name>::s_allEnums[] =                                                    \
  46. {                                                                                                                       \
  47. TO_ENUM_ITEM(enum_list)                                                                                             \
  48. };                                                                                                                      \
  49. template<> const char* EnumHelper<enum_type_name>::s_allEnumNames[_countof(EnumHelper<enum_type_name>::s_allEnums)];  \
  50. template<> char EnumHelper<enum_type_name>::s_singleEnumStr[] = STRINGIZE(enum_list) ;                                  \
  51. 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函数中增加一个其它功能(例如日志),那么开发的兄弟真的是要烦“死了”!!!

其实仔细一看,包括函数申明、函数定义、函数注册,每个类的代码除了类名外其它都是一模一样的,有没有简单的方法呢?

肯定是有的,这个方法就是宏了,按照如下方法定义宏:

[cpp] view plaincopy

  1. #define DECLARE_CLASS_CREATE(class_name) \
  2. static CObject* CreateClass## class_name ();
  3. #define IMPL_CLASS_CREATE(class_name) \
  4. static CObject* CreateClass## class_name (){ \
  5. return new class_name; \
  6. };
  7. #define REG_CLASS_CREATE(class_name) \
  8. 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

时间: 2024-10-11 02:12:40

C++实现反射机制的相关文章

Android小例子:使用反射机制来读取图片制作一个图片浏览器

效果图: 工程文件夹: 该例子可供于新手参考练习,如果有哪里不对的地方,望指正>-< <黑幕下的人> java代码(MainActivity.java): package com.example.imageswitchtest; import java.lang.reflect.Field; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.v

java反射机制(一)—— 利用反射机制实例化对象

一.Java有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载.探知.使用编译期间完全未知的classes.换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体.或对其fields设值.或唤起其methods.(度娘文库是这么说的) 二.这篇文章主要介绍一下通过反射机制去实例化一个类的对象,然后调用其方法.本文主要介绍两种方式,第一种就是通过构造函数来实例化,第二种就是通过Cl

Java反射机制

Java的反射机制概念 主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义. 反射是java中一种强大的工具,能够使我们很方便的创建灵活的代码,这些代码可以再运行时装配,无需在组件之间进行源代码链接.但是反射使用不当会成本很高! 反射的作用 1 可以反编译将class文件编译成java文件 2 可以通过反射机制访问Java对象的属性,方法,构造方法等 反射机制使用步骤 1 得到要调用类的class 2 通过得到的c

【java】java反射机制,动态获取对象的属性和对应的参数值,并属性按照字典序排序,Field.setAccessible()方法的说明【可用于微信支付 签名生成】

方法1:通过get()方法获取属性值 package com.sxd.test.controller; public class FirstCa{ private Integer num; private String name; private Boolean flag; public Integer getNum() { return num; } public void setNum(Integer num) { this.num = num; } public String getNam

使用反射机制调用属性和私有成员与代理模式的介绍

使用反射机制调用属性: 通过反射机制可以获得类的属性,获得到的属性同样的可以进行赋值.得值操作,调用getField方法并传递属性的名称可以获得[学Java,到凯哥学堂kaige123.com]指定的属性,调用getFields方法则可以获得全部属性,但是这种方式不能获得私有属性: 代码示例: Student类示例: 运行结果: 从运行结果可以看出只拿出了公开的属性,私有的属性拿不到. 使用反射机制调用私有成员: 1.调用私有属性 在反射机制里调用私有属性需要通过getDeclaredField

反射机制

反射机制: 反射机制是能够帮助我们把代码变得更加灵活,可扩展性更高,俗称"软编程.软写法".例如:有一个文件里面有一些值,想要把这些值赋值到一个Student类的属性中,按照以前所学到的知识点,只能是通过文件流将文件里的值读取出来,然后再通过set方法将这些值都赋给Student类的属性.但是,这种方式是将代码写死了,在这之后会有一个问题就是如果文件里的内容发生变动,或者要把这个Student类更换成Dog类的话,就要重新修改代码.程序写好了部署到服务器上运行了,总不能时不时就打开来修

Python学习心得(六) 反射机制、装饰器

1.反射机制 #/usr/bin/env python # -*- coding:utf-8 -*- ''' Python反射机制的核心本质:利用字符串的形式去对象(模块)中操作(查找/获取/删除/添加)成员,一种基于字符串的事件驱动 通俗讲就是通过用户传入url的不同,调用不同的模块函数,好多比较流行的web框架都是通过反射的机制,根据url的不同指向不同的模块 getattr(),hasattr(),setattr(),delattr()对模块的修改都在内存中进行,并不会影响文件中的真实内容

Java 反射机制

使用 Java 反射机制可以在运行时期检查 Java 类的信息,检查 Java 类的信息往往是你在使用 Java 反射机制的时候所做的第一件事情,通过获取类的信息你可以获取以下相关的内容: Class 对象 类名 修饰符 包信息 父类 实现的接口 构造器 方法 变量 注解 除了上述这些内容,还有很多的信息你可以通过反射机制获得,如果你想要知道全部的信息你可以查看相应的文档 JavaDoc for java.lang.Class 里面有详尽的描述. 在本节中我们会简短的涉及上述所提及的信息,上述的

Java的反射机制简述

反射机制是Java语言中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许内部的成员进行操作.虽然这个特性在实际开发中使用的不多,但是像Pascal.C和C++等语言根本没有提供这样的特性.由于反射机制能够实现在运行时对类进行装载,因此能够增加程序的灵活性,但是不恰当地使用反射机制也会严重影响系统的性能. 具体而言,反射机制提供的功能主要有: 1.得到一个对象所属的类: 2.获取一个类的所有成员变量和方法: 3.在运行时创建对象: 4.在运行时调用对象的方法. 其实,反射机制非常重要

Java中的反射机制

Java反射的概念 Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制 Java反射机制主要提供下面几种用途: 1.在运行时判断任意一个对象所属的类 2.在运行时构造任意一个类的对象 3.在运行时判断任意一个类所具有的成员变量和方法 4.在运行时调用任意一个对象的方法 首先看一个简单的例子,通过这个例子来理解Java的反射机制是如何工作的 i