前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++类成员反射实现

C++类成员反射实现

作者头像
sofu456
发布2020-02-18 16:13:53
1.5K0
发布2020-02-18 16:13:53
举报
文章被收录于专栏:sofu456sofu456

引出问题:

给定一个我们自己定义的类A,如何便捷的输出其所有的成员变量类型及值。

日常编程中有这样的需求,一个是为了调试,二个是为了在不同模块运行时得到类的属性用于显示及修改。但C++未提供原生的反射机制,所以有了以下思考。


核心的需求为:对于一个类型A,我们可以拥有其所有的类型信息。例如:

代码语言:javascript
复制
class A
{
    int INT;
    float FLT;
}

/*MAIN*/
A a;
a.INT = 1;
a.FLT = 2.0f;
{
    for(auto& var : a.attr())
        cout << var;
}

其输出结果应类似:
int INT 1
float FLT 2.0

找了一些资料来看,我们手头上已经有了一个实现版本,直接给出源码:

代码语言:javascript
复制
#include <iostream>

#include <vector>
#include <map>
#include <string>


#define _OFFSET_(_Obj_Ty,_Key)                                                    \
    ((unsigned long)(&((_Obj_Ty *)0)->_Key))

#define CLASS_REGISTER(_Obj_Ty)                                                    \
public:                                                                            \
    static tat::tat_class * get_class_ptr()                                        \
    {                                                                            \
        static tat::tat_class __class_##_Obj_Key##__;                            \
        return &__class_##_Obj_Key##__;                                            \
    }

#define FIELD_REGISTER(_Access,_Field_Ty,_Field_Key,_Obj_Ty)                    \
_Access:                                                                        \
    _Field_Ty _Field_Key;                                                        \
private:                                                                        \
    class __field_register_##_Field_Key##__                                        \
    {                                                                            \
    public:                                                                        \
        __field_register_##_Field_Key##__()                                        \
        {                                                                        \
            static tat::__field_register__ reg_##_Field_Key(                    \
                _Obj_Ty::get_class_ptr(),                                        \
                _OFFSET_(_Obj_Ty,_Field_Key),                                    \
                #_Field_Key);                                                    \
        }                                                                        \
    }_Field_Key##_register;

namespace tat
{
    class tat_field
    {
    private:
        unsigned long _offset;
        std::string _key;
    public:
        tat_field(unsigned long offset, std::string key) :_offset(offset), _key(key) {}
        tat_field(const tat_field &field)
        {
            this->_offset = field._offset;
            this->_key = field._key;
        }
    public:
        template<typename _Obj_Ty, typename _Value_Ty>
        void get(_Obj_Ty *obj, _Value_Ty &value)
        {
            value = *((_Value_Ty *)((unsigned char *)obj + _offset));
        }
        template<typename _Obj_Ty, typename _Value_Ty>
        void set(_Obj_Ty *obj, const _Value_Ty &value)
        {
            *((_Value_Ty *)((unsigned char *)obj + _offset)) = value;
        }
        std::string get_key() const
        {
            return this->_key;
        }
    };

    class tat_class
    {
    private:
        std::map<std::string, tat_field> _field_map;
        std::string _key;
    public:
        std::map<std::string, tat_field> get_fields()
        {
            return this->_field_map;
        }
        tat_field get_field(std::string key)
        {
            std::map<std::string, tat_field>::iterator itr = _field_map.find(key);
            return (*itr).second;
        }
        void add_field(const tat_field &field)
        {
            _field_map.insert(std::pair<std::string, tat_field>(field.get_key(), field));
        }
    };

    class __field_register__
    {
    public:
        __field_register__(tat_class *class_ptr, unsigned long offset, std::string key)
        {
            tat_field field(offset, key);
            class_ptr->add_field(field);
        }
    };

};


class TestClass
{
public:
    TestClass() = default;
    ~TestClass() = default;

    CLASS_REGISTER(TestClass)
        FIELD_REGISTER(public, long, _long_f, TestClass)
        FIELD_REGISTER(public, int, _int_f, TestClass)
        FIELD_REGISTER(public, std::string, _str_f, TestClass)
        FIELD_REGISTER(public, std::vector<int>, _vec_f, TestClass)
};



void main()
{
    TestClass inst;

    tat::tat_class *test_class = TestClass::get_class_ptr();
    std::map<std::string, tat::tat_field> field_map = test_class->get_fields();


    for (auto& var : field_map)
    {
        std::cout << var.first << std::endl;
    }

    tat::tat_field test_vec_field = field_map.find("_vec_f")->second;
    std::vector<int> vec;
    test_vec_field.get(&inst, vec);
    vec.push_back(22);
    test_vec_field.set(&inst, vec);
    std::cout << inst._vec_f[0] << std::endl;
}

首先是#define CLASS_REGISTER(_Obj_Ty)宏,该宏在类体中加入了一个静态函数

代码语言:javascript
复制
#define CLASS_REGISTER(_Obj_Ty)                                                    \
public:                                                                            \
    static tat::tat_class * get_class_ptr()                                        \
    {                                                                            \
        static tat::tat_class __class_##_Obj_Key##__;                            \
        return &__class_##_Obj_Key##__;                                            \
    }

所以我们可以通过类名或实例来获取到反射信息的指针。这里静态局部实例为__class_CLASSNAME__,他是一个tat::tat_class类型的值。那么我们接下来看tat::tat_class的定义。

代码语言:javascript
复制
    class tat_class
    {
    private:
        std::map<std::string, tat_field> _field_map;
        std::string _key;
    public:
        std::map<std::string, tat_field> get_fields()
        {
            return this->_field_map;
        }
        tat_field get_field(std::string key)
        {
            std::map<std::string, tat_field>::iterator itr = _field_map.find(key);
            return (*itr).second;
        }
        void add_field(const tat_field &field)
        {
            _field_map.insert(std::pair<std::string, tat_field>(field.get_key(), field));
        }
    };

很明显可以看出,类中用一个Map去保存了我们需要的信息。公有的几个函数都是去查找或者添加对应的信息使用。而Map中的值是以tat::tat_field这个类去保存的。接下来看看这个类的实现。

代码语言:javascript
复制
    class tat_field
    {
    private:
        unsigned long _offset;
        std::string _key;
    public:
        tat_field(unsigned long offset, std::string key) :_offset(offset), _key(key) {}
        tat_field(const tat_field &field)
        {
            this->_offset = field._offset;
            this->_key = field._key;
        }
    public:
        template<typename _Obj_Ty, typename _Value_Ty>
        void get(_Obj_Ty *obj, _Value_Ty &value)
        {
            value = *((_Value_Ty *)((unsigned char *)obj + _offset));
        }
        template<typename _Obj_Ty, typename _Value_Ty>
        void set(_Obj_Ty *obj, const _Value_Ty &value)
        {
            *((_Value_Ty *)((unsigned char *)obj + _offset)) = value;
        }
        std::string get_key() const
        {
            return this->_key;
        }
    };

从类的定义中,我们可以看出。类保存的关键值就是两个,一个是属性对应类的偏移,一个是属性对应的名称。其公有接口利用模板实现了类型的判断,对于属性的类型,我们就不需要手动去声明了。那么现在的问题就只有如何在我们定义的时候获取到对应属性的偏移了。最终的宏命令

代码语言:javascript
复制
#define FIELD_REGISTER(_Access,_Field_Ty,_Field_Key,_Obj_Ty)                    \
_Access:                                                                        \
    _Field_Ty _Field_Key;                                                        \
private:                                                                        \
    class __field_register_##_Field_Key##__                                        \
    {                                                                            \
    public:                                                                        \
        __field_register_##_Field_Key##__()                                        \
        {                                                                        \
            static tat::__field_register__ reg_##_Field_Key(                    \
                _Obj_Ty::get_class_ptr(),                                        \
                _OFFSET_(_Obj_Ty,_Field_Key),                                    \
                #_Field_Key);                                                    \
        }                                                                        \
    }_Field_Key##_register;

我们在定义类成员时,使用的是FIELD_REGISTER(public, long, _long_f, TestClass),对应宏来看他到底做了什么。

首先宏将对应值的public属性放在了最前面,使得对应定义的值为公有。然后第二个参数和第三个参数为值的类型及标识,拓展以后就变成了原生C++的定义方式。最重要的是,在此之后,宏生成了一个私有的类,类的名称为 __field_register_##_Field_Key##__,并生成了一个实例_Field_Key##_register。这个类只有一个构造函数。构造函数中,生成了一个静态对象static tat::__field_register__ reg__long_f(...),这个tat::__field_register__的类型是最后一个类,定义如下:

代码语言:javascript
复制
    class __field_register__
    {
    public:
        __field_register__(tat_class *class_ptr, unsigned long offset, std::string key)
        {
            tat_field field(offset, key);
            class_ptr->add_field(field);
        }
    };

构造函数中,传进了一个tat_class的指针,一个偏移,和一个属性名称。然后构造函数会构造一个tat::tat_field,并将其加入到tat_class中。

所以在上面,我们最终就会把对应的类型信息加入到map中,最后就可以通过map获取到属性的名称,偏移。从而修改其值。(是不是很简单 - -!)

我觉得最黑科技的宏就是如何去取对应属性的偏移量这个宏,我诈一看认为会非法内存,没想到竟然不会。我们来看看:

代码语言:javascript
复制
#define _OFFSET_(_Obj_Ty,_Key)                                                    \
    ((unsigned long)(&((_Obj_Ty *)0)->_Key))

首先将0强转成class *的指针,然后去取对应属性的地址。这样我们取到的值就是对于0的偏移量也就是对应属性实际的偏移量了。(好黑科技啊。。。。)

理解完这个套路以后,我们就可以相应的实现一些我们自己的反射系统了。OK

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档