谈谈C++如何实现反射机制

反射是指程序在运行时动态获取对象属性与方法的一种机制,即编译器需要将类型信息(属性类型与偏移地址以及成员函数的地址等信息)编译到程序文件中,当程序运行时将这些信息加载到内存中去,做到运行时只根据对象的地址或引用就可以获取到对象的类型信息,从而利用这些信息达到修改或重建对象的目标。

21世纪以前内存与带宽一直是非常昂贵的资源,编译器在生成可执行文件时要兼顾系统内存等硬件资源,为节省成本不会将类型信息加载到运行内存中去。网络传输也要考虑带宽与各节点的性能,往往需要各通讯节点间采用紧致、简单、高效的通讯协议,这期间编程世界是C/C++的天下,网络上传输的是C/C++语言中的结构体,把结构体当作通讯协议具有无需序列化与反序列化的优点,完美契合当时的软硬件环境。

随着时间的推移,硬件性能遵循“摩尔定律”发展,网络带宽也大幅提高,万维网验证了纯文本的http协议是一个简单、易理解的通讯协议,这时软件工程的主要矛盾已不是需求与硬件之间矛盾,而是开发者与不断变更的需求、逐渐多元化的系统之间矛盾,这时开发者需要一个不受语言制约、可读性强、易调试的通讯协议。

由于C/C++中的结构存在可读性差,难以调试,协议升级困难等缺点,导致xml与json等自注释文本协议得以发展普及。但文本协议与对象内存模型存在差异,服务在收到请求后需要将请求报文反序列化为具体对象,业务处理完成后将输出结果序列化为通讯报文在网络上传输,整个处理过程中序列化与反序列化跟业务没什么关系,但开发者需要写大量重复代码去完成序列化与反序列化的工作,Java为解决这个问题添加了反射机制,将类型信息编译到class文件中,程序启动时将类型信息加载到内存,运行时可以动态获取、修改对象的属性名称与取值,从而利用这些类型信息提供统一的序列化与反序列化功能。

一直以来,C++都未能支持反射机制,在C++中要实现类似Java等语言的反射机制需要另外写代码保存类型相关信息,然后在运行时使用。后来C++引入了typeid运算符,可以在运行时获取类型相关信息,说明C++在编译时是会保存类型相关信息的,只是C++标准要求保存的运行时类型信息不足以支撑类似Java的反射机制。下面给出一个简单的例子,说明C++程序在运行时获取类型相关信息。点击查看测试代码

class Object
{
public:
    //用于在运行时获取对象的真实类型名称
    virtual string getClassName() const
    {
#ifdef _MSC_VER
        return typeid(*this).name() + 6;
#else
        const char* name = typeid(*this).name();

        while (*name >= '0' && *name <= '9') name++;

        return name;
#endif
    }
};

通过上述代码你会发现不同编译器中typeid获取的信息格式是不一样的,C++标准并没有明确定义typeid运算符返回的信息格式。typeid是一个很特殊运算符,对于没有虚函数表指针的对象是在编译期间确定类型信息的,对于有虚函数表指针的对象在运行时才能准确的确定类型信息,所有typeid行为一方面像运算符受编译器控制,另一方面也在编译器的掌控之外,需要在具体运行时才能确定类型信息。

typeid运算符是C++支持反射的一个试水,后续C++肯定会借鉴Java等语言的反射功能,最终将反射机制纳入到C++标准。

C++编译器完全可以将类型信息与虚函数表一起保存,在现有虚函数表里面附加类型信息,然后可以通过对象的虚函数表针对找到对象的类型信息。这种方案不会破坏C++原有的对象内存模型,只是拓展了虚函数表的功能。

当然,我们也可以写额外的代码来保存类型信息,然后在运行时获取保存的类型信息来实现反射功能。下面我们实现一个ReflectHelper类,该类以map的方式管理类成员的名称、类型、偏移地址等信息,map键值为类的名称。

/*
* 反射属性管理类
* 以map方式存储相关类成员变量的类型、偏移地址等信息
*/
class ReflectHelper
{
protected:
    static mutex& GetMutex()
    {
        static mutex mtx;
        return mtx;
    }
    static map<string, set<ReflectItem>>& GetMap()
    {
        static map<string, set<ReflectItem>> map;
        return map;
    }

public:
    //通过构造函数将相关类成员的信息记录在反射信息map中
    ReflectHelper(Object* self, void* data, const char* type, const char* name)
    {
        static map<string, set<ReflectItem>>& attrmap = GetMap();

        lock_guard<mutex> lk(GetMutex());
        string key = self->getClassName();

        if (type == "int" || type == "bool" || type == "float" || type == "double" || type == "string")
        {
            attrmap[key].insert(ReflectItem((char*)(data)-(char*)(self), type, name));
        }
        else
        {
            attrmap[key].insert(ReflectItem((char*)(data)-(char*)(self), "object", name));
        }
    }

public:
    //根据类名称获取成员变量反射信息列表
    static vector<ReflectItem> GetList(const string& key)
    {
        static map<string, set<ReflectItem>>& attrmap = GetMap();

        vector<ReflectItem> vec;
        lock_guard<mutex> lk(GetMutex());

        auto it = attrmap.find(key);

        if (it == attrmap.end()) return {};

        for (auto& item : it->second) vec.push_back(item);

        std::sort(vec.begin(), vec.end(), [](const ReflectItem& a, const ReflectItem& b){
            return a.getOffset() < b.getOffset();
        });

        return std::move(vec);
    }
    //获取指定类成员变量的反射信息
    static ReflectItem GetItem(const string& key, const string& name)
    {
        static map<string, set<ReflectItem>>& attrmap = GetMap();

        ReflectItem item(0, NULL, name);
        lock_guard<mutex> lk(GetMutex());

        auto it = attrmap.find(key);

        if (it == attrmap.end()) return item;

        auto tmp = it->second.find(item);

        if (tmp == it->second.end()) return item;

        return *tmp;
    }
};

接着我们定义以下宏,用于定义反射成员变量,这些宏可以在构造对象时将类成员的名称、类型、偏移地址等信息保存在上面所说的map中,以便程序运行时使用。

/*
* 以下宏用来定义需要反射的类成员
*/
#define reflect_attr(type, name)   \
type name;                         \
const ReflectHelper __##name = ReflectHelper(this, &this->name, #type, #name)

#define reflect_int(name) reflect_attr(int, name)
#define reflect_bool(name) reflect_attr(bool, name)
#define reflect_float(name) reflect_attr(float, name)
#define reflect_double(name) reflect_attr(double, name)
#define reflect_string(name) reflect_attr(string, name)

下面的ReflectItem类用来记录、管理一个反射成员信息与取值。

/*
* 反射属性操作类
* set方法用于修改属性
* get方法用于获取属性
*/
class ReflectItem
{
protected:
    int offset;
    string name;
    const char* type;


public:
    ReflectItem() : offset(0), type(NULL)
    {
    }
    ReflectItem(int _offset, const char* _type, const string& _name) : offset(_offset), type(_type), name(_name)
    {
    }

public:
    bool canUse() const
    {
        return type ? true : false;
    }
    bool operator < (const ReflectItem& obj) const
    {
        return name < obj.name;
    }
    bool operator == (const ReflectItem& obj) const
    {
        return name == obj.name;
    }

public:
    //获取成员的偏移地址
    int getOffset() const
    {
        return offset;
    }
    //获取成员名称
    string getName() const
    {
        return type ? name : "";
    }
    //获取成员类型
    string getType() const
    {
        return type ? type : "";
    }
    //获取成员的值
    string get(const void* obj) const
    {
        char* dest = (char*)(obj) + offset;

        if (type == NULL || type == "object") "";

        if (type == "int") return to_string(*(int*)(dest));

        if (type == "bool") return *(bool*)(dest) ? "true" : "false";

        if (type == "float") return to_string(*(float*)(dest));

        if (type == "double") return to_string(*(double*)(dest));

        return *(string*)(dest);
    }
    //修改成员的值
    bool set(void* obj, int val) const
    {
        char* dest = (char*)(obj) + offset;

        if (type == NULL || type == "object") return false;

        if (type == "int")
        {
            *(int*)(dest) = val;
        }
        else if (type == "bool")
        {
            *(bool*)(dest) = (val ? true : false);
        }
        else if (type == "float")
        {
            *(float*)(dest) = val;
        }
        else if (type == "double")
        {
            *(double*)(dest) = val;
        }
        else
        {
            *(string*)(dest) = to_string(val);
        }

        return true;
    }
    //修改成员的值
    bool set(void* obj, bool val) const
    {
        return set(obj, val ? 1 : 0);
    }
    //修改成员的值
    bool set(void* obj, float val) const
    {
        return set(obj, (double)(val));
    }
    //修改成员的值
    bool set(void* obj, double val) const
    {
        char* dest = (char*)(obj) + offset;

        if (type == NULL || type == "object") return false;

        if (type == "int")
        {
            *(int*)(dest) = val;
        }
        else if (type == "bool")
        {
            *(bool*)(dest) = val < -0.000001 || val > 0.000001;
        }
        else if (type == "float")
        {
            *(float*)(dest) = val;
        }
        else if (type == "double")
        {
            *(double*)(dest) = val;
        }
        else
        {
            *(string*)(dest) = to_string(val);
        }

        return true;
    }
    //修改成员的值
    bool set(void* obj, const char* val) const
    {
        char* dest = (char*)(obj) + offset;

        if (val == NULL || type == NULL || type == "object") return false;

        if (type == "string")
        {
            *(string*)(dest) = val;
        }
        else
        {
            if (*val == 0) return true;

            if (type == "int")
            {
                *(int*)(dest) = atoi(val);
            }
            else if (type == "bool")
            {
                string tmp = val;

                *(bool*)(dest) = (tmp == "true");
            }
            else if (type == "float")
            {
                *(float*)(dest) = atof(val);
            }
            else
            {
                *(double*)(dest) = atof(val);
            }
        }

        return true;
    }
    //修改成员的值
    bool set(void* obj, const string& val) const
    {
        return set(obj, val.c_str());
    }
};

反射机制就说到这里,上面代码很简单,有兴趣的同学可以查看我的完整代码。点击查看完整代码

编辑于 04-30