# 导读(反射简介)

ReflectionJava 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说 "自审",并能直接操作程序的内部属性。即可以在运行时加载、探知、使用编译期间完全未知的 classes 。换句话说, Java 程序可以加载一个运行时才得知名称的 class ,获悉其完整构造(但不包括 methods 定义),并生成其对象实体、或对其 fields 设值、或唤起其 methods

即有类似如下语句

ClassA objA = ReflexCreat("ClassA")

这也是程序序列化的关键,可以吧相关的类对象以字符串的形式存贮到磁盘,然后利用反射机制读取序列化文件还原系统状态

然而遗憾的是 C++ 并不支持这一机制,但是在实际使用中这是一个很 nice 的功能,对与 C++fans 来说这个可是一个不怎么好的消息

但是考虑到 MFC 中的 序列化 功能, C++ 肯定也是有办法实现反射的功能的,下面就一起来探讨下 C++ 反射机制的实现

# 一、实现(简单工厂)

学过设计模式的人对于 简单工程模式 肯定不会陌生,我们可以通过建立一个简单工厂模式,利用工厂通过类型名称帮助我们得到类的实体对象

代码形式如下

Class CFactory
{
public:
    Object* FactoryCreater(const std::string&  class_name)
    {
        if (class_name == "ClassA")
            return new ClassA;
        else if (class_name == "ClassB")
            return new ClassB;
        ...
        else return NULL;
    }
};

客户端 Client 直接如下使用就好了

int main()
{
    CFactory factory;
    ClassA objA = factory.FactoryCreater("ClassA");
    ...
    return 0;
}

这样好像我们实现了反射这一简单功能,但是仔细想想这么做仿佛有所不妥,每添加一个新的类,我们就需要在工厂方法中添加分支语句,这样做不但使得代码复杂化,同时也违背了设计模式的开闭原则,所以我们需要换一种方法来解决这个问题

# 二、实现 & 进阶(自动注册 hash 表,回调函数)

需要解决的几个要点

  • 定义回调函数指针,指向创建类实例的回调函数

  • 一个带有 hash 表的工厂类,用 ' 类的名称 ' 和 ' 指向创建类实例的回调函数的函数指针 ' 作为键值对

  • 实现 hash 表的自动注册

如果实现了以上几点,基本上 C++ 下简单的反射功能就完成了

现在我们来一步一步解决以上问题(将整个反射的实现封装到 Reflex.h 和 Reflex.cpp 中)

  • 带有 hash 表的 Reflex 类的实现 (也是需要反射支持的类的基类)
//Reflex.h
class ClassInfo;
class Reflex
{
public:
    Reflex() {}
    virtual ~Reflex() {}
    //hash 表注册
    static bool Register(ClassInfo* pCInfo);
    static Reflex* CreatObject(std::string className);
};
//Reflex.cpp
static std::map<std::string, ClassInfo*> *m_classInfoMap = NULL;
bool Reflex::Register(ClassInfo* pCInfo)
{
    if (!m_classInfoMap)
    {
        m_classInfoMap = new std::map<std::string, ClassInfo*>();
    }
    if (!pCInfo)
    {
        return false;
    }
    if (m_classInfoMap->end() == m_classInfoMap->find(pCInfo->m_className))
    {
        m_classInfoMap->insert(std::map<std::string, ClassInfo*>::value_type(pCInfo->m_className, pCInfo));
    }
    return true;
}
Reflex* Reflex::CreatObject(std::string className)
{
    std::map<std::string, ClassInfo*>::const_iterator c_iter = m_classInfoMap->find(className);
    if (m_classInfoMap->end() != c_iter)
    {
        // 当传入字符串 name 后,通过 name 找到 info, 然后调用对应的 CreatObject () 即可
        return c_iter->second->CreateObject();
    }
    return NULL;
}
  • 定义回调函数函数指针,完成 hash 表自主注册的 ClassInfo 类
//Reflex.h
// 函数指针、指向创建类实例的回调函数
typedef Reflex* (*ObjConstructorFun)();
class ClassInfo
{
public:
    ClassInfo(const std::string className, ObjConstructorFun classConstructor)
        :m_className(className), m_objectConstructor(classConstructor)
    {
        //classInfo 的构造函数是传入类名和类对应的 new 函数然后自动注册进 map 中
        Reflex::Register(this);
    }
    virtual ~ClassInfo() {}
    Reflex* CreateObject()const { return m_objectConstructor ? (*m_objectConstructor)() : NULL; }
    bool IsDynamic()const { return NULL != m_objectConstructor; }
    const std::string GetClassName()const { return m_className; }
    ObjConstructorFun GetConstructor()const { return m_objectConstructor; }
public:
    std::string m_className;
    ObjConstructorFun m_objectConstructor;
};

好了,到目前位置我们的 局 已经铺的差不多了,现在就是具体怎么用上我们的 反射模块

方法如下:

  • 新定义的类若需要 反射支持 需要继承 基类 Reflex

  • 在新类中定义并实现 返回自身对象指针的函数,用来作为回调函数指针指向的函数

  • 持有一个静态 ClassInfo 对象,并使用类名和函数指针初始化,完成 hash 表的注册

需要反射支持的 CTest 类的申明如下

//Test.h
#include "Reflex.h"
class CTest : public Reflex
{
public:
    CTest();
    virtual ~CTest();
    virtual ClassInfo* GetClassInfo() const { return &m_classInfo; }
    static Reflex* CreatObject() { return new CTest; }
protected:
    static ClassInfo m_classInfo;
};
//Test.cpp
ClassInfo CTest::m_classInfo("CTest", CTest::CreatObject);
CTest::CTest()
{
    std::cout <<"ADDR: ["<< std::hex << (long)this
        << "] ,The Object Name is \"CTest\" construced!" << std::endl;
}
CTest::~CTest()
{
    std::cout <<"ADDR: ["<< std::hex << (long)this
        << "] ,The Object Name is \"CTest\" destroyed!" << std::endl;
}
  • 客户端 Client 调用情况如下
int main()
{
    CTest* test = (CTest*)(Reflex::CreatObject("CTest"));
    delete test;
    return 0;
}
// 输出情况:
ADDR: [7b4d40] ,The Object Name is "CTest" construced!
ADDR: [7b4d40] ,The Object Name is "CTest" destroyed!

# 三、代码简化 & 宏(降低使用成本)

通过之前的代码我们已经在 C++ 中实现了反射,但是可能大家发现了,每一个需要反射支持的类,我们都需要额外的添加一些代码,来保证反射模块的正常运作,而且这一部分除了类的名称之外就没有了什么不同。

聪明的你可能已经想到了,没错就是 C+ + 的宏,虽然 C++ 的作者极力反对宏的使用,但是那啥你懂的(懒人就是理由多~),当然之前学习 Windwos 程序设计的时候,在 MFC 中微软的小哥哥们那才叫把宏玩的了个溜

  • 言归正传,在基类 Reflex.h 中加入这一大段宏
// 新申明类成员函数以及变量的注册
#define DECLARE_CLASS(class_name) \
    public:\
        virtual ClassInfo* GetClassInfo() const { return &m_classInfo; }\
        static Reflex* CreatObject()\
        {\
            return new class_name;\
        }\
    protected:\
        static ClassInfo m_classInfo;
// 新申明类 ClassInfo 注册
#define REGISTER_CLASS(class_name)\
    ClassInfo class_name::m_classInfo(#class_name, class_name::CreatObject);
// 利用自写反射生成类对象
#define REFLEX_CLASS(class_name)\
    (class_name*)(Reflex::CreatObject(#class_name))
  • 于是乎,新申请的类就可以简化成酱紫
//Test.h
class CTest : public Reflex
{
public:
    CTest();
    virtual ~CTest();
    DECLARE_CLASS(CTest)
}
//Test.cpp
REGISTER_CLASS(CTest)
CTest::CTest()
{
}
CTest::~CTest()
{
}

# 四、结语

在这儿说一下宏里面的东西吧

  • 最简单的 \ 就是当前宏的定义除了本行下面还有,告诉编译器后面的一起处理

#define OUT(name) printf(#name);

  • 以这个为例其中的 #name 之前的 # 就表示把 name 格式化为字符串

#define T(name) Class## name

  • 这里的 ## name 中的 ## 就是连接符的意思,Class 和可变字符 name 连接成一个字符串

到此 C++ 中简单反射的实现就告一段落,若有错误,欢迎指正

更新于 阅读次数