C++下实现机制的反射「2」——升级自省功能

上一篇「part1」中,实现了一个能够存储类的成员变量的结构,使用父类Base中的set_field方法,将派生类的成员变量映射进map中,所以我们只需要在Base类中编写一堆方法就能直接调用派生类的成员变量。

例如:

  • 导出成JSON格式数据
string to_json()
{
    string json = "{";
    for(auto i = m_table.begin(); i != m_table.end();)
    {
        json.append("\"");
        json.append(i->first);
        json.append("\":");
        switch(i->second.first)
        {
            case INT_TYPE:
                json.append(to_string(*static_cast<int*>(i->second.second)));
                break;
            case DOUBLE_TYPE:
                json.append(to_string(*static_cast<double*>(i->second.second)));
                break;
            case STRING_TYPE:
                json.append("\"");
                json.append(*static_cast<string*>(i->second.second));
                json.append("\"");
                break;
        }
        if(++i != m_table.end())
        {
            json.append(",");
        }
    }
    json.append("}");
    return json;
}

由于保存指针的类型是void *,所以在使用的时候都需要对其使用static_cast转化一下,使用强制转换也是可以的,然而因为指针所以是比较危险的操作,推荐还是用cast转化指针类型,才能正确的获取值。就这样不需要每个类都要重写一个方法,就能很愉快的节省时间,如果是QT框架的话还可以直接转化成QJsonObject更方便。

升级

在之前实现的注册成员函数的方法需要传入3个list,并且这三个list还都得手动写,而且连数据类型都得自己手动写上去,这样虽然后期一时爽,初始化火葬场。比如我的一个类型有27个成员变量,光这个累的初始化函数都写了半天,而且还得手动检查一遍顺序和名字有没有写对,这无异是非常低效的方法,所以我们得使用新的方法替换这一段初始化函数。

typeinfo

是存在于std标准库,可以获取类型的类型信息。或许会有人说「哇!c++不是提供了能获取类信息的东西吗!那你带我们之前写的是P嘞!」。虽然这typeinfo是官方提供的提取类型的一个东西,然而实际能获取的信息十分有限。

class type_info {
public:
  virtual ~type_info();
  bool operator==(const type_info& rhs) const noexcept;
  bool operator!=(const type_info& rhs) const noexcept;
  bool before(const type_info& rhs) const noexcept;
  size_t hash_code() const noexcept;
  const char* name() const noexcept;
  type_info(const type_info& rhs) = delete; // 不能复制
  type_info& operator=(const type_info& rhs) = delete; // 不能复制
};

我们可以从该类型数据的定义中看到,几乎就只有一个name是可以用的数据。然而,当C++文件经过编译器处理之后,返回的却不是类名而是一串乱码,实际上type_info.name返回的数据是an implementation-defined NTBS.是一串以空截断的字符序列,因此你就不能完全依靠这东西来处理。

不过即使返回的不是正确的类型名我们还是可以拿来用于判断数据类型。

qDebug() << (typeid(&this->username) == typeid(string));
qDebug() << (typeid(&this->username) == typeid(string*));
qDebug() << (typeid(this->username) == typeid(string&));
qDebug() << (typeid(int) == typeid(this->id));
qDebug() << (typeid(int) == typeid(this->username));
#returns
false
true
true
true
false

我们可以看到typeinfo可以正确的处理指针和引用的数据类型。所以根据这个东西我们我们可以开始升级我们的初始化函数。

新的初始化函数

template<typename T>
void set(string field, T* t)
{
    if(typeid(t) == typeid(int*))
    {
        m_table.insert(FIELD(field, VALUE(INT_TYPE, t)));
    }
    else if(typeid(t) == typeid(string*))
    {
        m_table.insert(FIELD(field, VALUE(STRING_TYPE, t)));
    }
    else if(typeid(t) == typeid(double*))
    {
        m_table.insert(FIELD(field, VALUE(DOUBLE_TYPE, t)));
    }
}

使用模版类就不需要一个统一的数据类型就像之前的void *一样传入指针,那样会丢弃掉指针的数据类型,无法简单的得到void *到底是一个什么东西。初始化直接使用set('username',&this->username);减少了一个参数。