【项目四】C++实现反射机制(通过宏和包装Lambda表达式实现)

前言

        昨天彻底完善了C++实现事件委托,与观察者模式有关的学习总算可以结束了,于是我翻开了《大话设计模式》的下一页,抽象工厂,令人无语的是其中又用到了C++中没有的技术——反射机制。既然没有,秉承着自己动手丰衣足食的理念,我决定自己写。

        反射机制,实际上就是通过类的字符串来创建类的实例的机制,可以说是广义的简单工厂模式,解决了简单工厂模式拓展困难的问题(简单工厂模式内需要通过if else来建立类名字符串和类的实例化的映射关系,因此修改是否麻烦)

        代码的实现已经放到仓库中了:

        AntonaStandard: 自定义的库,里面编写了一些常用的工具 (gitee.com)

探索与实现

        既然是建立映射,那么首先可以想到的是用标准库中的map来实现一一映射的关系。不过比较麻烦的一点就是类的构造函数是没有办法显式调用的,而且对于non-class的对象是没有构造函数的,因此需要想想别的办法。通过查找资料我找到了一种实现办法:

        (1条消息) C++反射机制的实现_陈 洪 伟的博客-CSDN博客_c++ 反射机制

        上面博客提到的办法是去为每一个注册的类或类型声明一个全局的回调函数(可以认为是一个代理),通过这个回调函数来实现对象的实例化。所有的声明过程都可以定义成一个宏。后期通过调用宏来实现不同的回调函数的声明。

#define REPLACE(className)  \
    className* create_##className{       \
        return new className;           \
    }                                   \
REPLACE(int)			// 使用宏,此时就定义了一个函数,名为int* create_int();

        宏还有一个很奇特的操作: # 的功能是将其后面的宏参数进行字符串化操作,简单说就是在对它所引用的宏变量通过替换后在其左右各加上一个双引号。## 功能是在带参数的宏定义中将两个子串联接起来,从而形成一个新的子串。

        然后只需要在需要反射调用的类的外面使用这个宏,就可以完成相关的回调函数声明。

        这个方法十分的巧妙,但是我觉得扩展性并不好,而且不便管理。另外在全局声明回调函数其实是一个很糟糕的决定,其一是没有办法访问控制,其二是污染了全局的声明。因此我想是否可以在局部进行注册,但是宏只是简单的代码替换,如果在其它的函数中使用上面的宏,会因为函数无法在其它函数中声明定义而保存。于是我将目光同放到了Lambda表达式,Lambda表达式是C++11的新特性,它允许匿名以及在函数内声明 “函数”

        而且lambda表达式的返回值可以由编译器推导,还可以通过包装器function来包装,这样的话我们的映射表map就可以声明为string对function的映射。

        那么对于包装器我们应该声明成生么样子的呢?

        对于返回值是void*的函数,编译器在用户return结果的时候允许任何其它类型的指针隐式转化成void*以返回。因此void* 常被用在不确定具体返回的指针类型的场景中(STL的源码中会比较多)。因此包装器function 可以声明成下面的形式:

function<void*(void)>
function<void*()>

map就声明成下面的形式

std::map<std::string,std::function<void*(void)>>

那么我们的宏的写法就十分明了了:即声明一个包装器,用这个包装器去包装我们用于生成实例对象的lambda表达式,然后利用宏的特殊字符#提取输入的类型的字符串,将二者一起插入到map中:

// 变量初始化宏
#define INIT                                    \
std::function<void*(void)> f;                   \
std::string str_name;                           \

// lambda表达式宏,用于创建类型className的构造回调函数,并通过包装器将其插入到map中

#define REGISTER(className)                     \
f =  [](){                                      \
        return new className;                   \
};                                              \
str_name = #className;                          \
this->func_map.insert(pair<std::string,std::function<void*(void)>>(str_name,f));  \

由于宏REGISTER的部分需要重复使用,为了防止出现重复声明的错误,将包装器和字符串的声明单独写在另一个宏INIT中。

        最后来解决一下拓展性的问题,我的想法是将反射声明为抽象类,由客户端去重写load方法进行相关类型的注册,这些派生类还可以继续派生以注册更多的类(只需要调用其基类的load函数即可)。

        

         下面给出反射的完整源码(也可以到我的仓库去克隆AntonaStandard的完整项目(反射依赖于我编写的异常类库))

        Reflection.h

#ifndef REFLECTION_H
#define REFLECTION_H
#include <map>
#include <functional>
#include <string>
#include "Exception.h"


#define AntonaStandard_Reflection_VERSION "1.0.0"
#define AntonaStandard_Reflection_EDIT_TIME  "2023/1/2"
#define AntonaStandard_Reflection_AUTHOR "Anton"

/*
*   Decoded by utf-8
*   2023/1/2  1.1.0 - 初步实现反射机制
*/

// 变量初始化宏
#define INIT                                    \
std::function<void*(void)> f;                   \
std::string str_name;                           \

// lambda表达式宏,用于创建类型className的构造回调函数,并通过包装器将其插入到map中

#define REGISTER(className)                     \
f =  [](){                                      \
        return new className;                   \
};                                              \
str_name = #className;                          \
this->func_map.insert(pair<std::string,std::function<void*(void)>>(str_name,f));  \

namespace AntonaStandard{
    // 抽象类,需要用户去派生
    class Reflection{
    protected:
        std::map<std::string,std::function<void*(void)>> func_map;
    public:
        Reflection(){};
        virtual void load()=0;
        virtual void* createInstance(const char* name_str);
        virtual void* createInstance(const std::string& name_str);
    };
} // namespace AntonaStandard
#endif 

Reflection.cpp

#include "Reflection.h"
#include <sstream>
namespace AntonaStandard{
    void* Reflection::createInstance(const char* name_str){
             // 检查key即name_str是否存在
        if(this->func_map.count(name_str)==0){
            // 不存在抛出异常
            std::stringstream ss;
            ss<<"The type you create was not registered!: "<<name_str;
            throw AntonaStandard::NotFound_Error(ss.str().c_str());
        }
        return this->func_map[name_str]();              // 调用对应的回调函数
    }
    
    void* Reflection::createInstance(const std::string& name_str){
         // 检查key即name_str是否存在
        if(this->func_map.count(name_str)==0){
            // 不存在抛出异常
            std::stringstream ss;
            ss<<"The type you create was not registered!: "<<name_str;
            throw AntonaStandard::NotFound_Error(ss.str().c_str());
        }
        return this->func_map[name_str]();              // 调用对应的回调函数
    }
}

给出使用示例:

#include <iostream>
#include "Reflection.h"
#include "Exception.h"
using namespace std;
using namespace AntonaStandard;
class A{
public:
    A(){
        cout<<"A构造了"<<endl;
    };
};
class B{
public:
    B(){
        cout<<"B构造了"<<endl;
    };
};
class Factory:public Reflection{
public:
    Factory():Reflection(){this->load();};
    virtual void load()override{
        INIT;                       // 初始化
        REGISTER(A);                // 注册类型A
        REGISTER(B);                // 注册类型B
    }
};
int main(){
    Factory fac;
    A* a  = (A*)fac.createInstance("A");
    B* b = (B*)fac.createInstance("B");
    fac.createInstance("C");
   
}

输出:

A构造了
B构造了
terminate called after throwing an instance of 'AntonaStandard::NotFound_Error'
  what():  The type you create was not registered!: C

总结

        本次这个反射的实现完成度还是比较高的,基本十分还原C#中的反射,以下给出《大话设计模式》中的反射,可以对比一下:

using System.Reflection;
IUser result = (IUser)Assembly.Load("抽象工厂模式").CreateInstance("抽象工厂模式.SqlserverUser");

不过缺点在于需要认为手动去注册类型,我暂时还没有找到能够批量自动化注册的方法。如果您有什么好的想法也可以私信或者参与到我的开源项目AntonaStandard中。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学艺不精的Антон

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值