Static Reflection and Serialization in C++

SetUp

在我们可以制造一个序列化函数之前,我们第一步需要去制作一些需要的可用的数据对我们,在我们的程序执行期间。
我们可以定义一个基本数据结构,它将保存这个信息。

struct Type{
	std::string stringName;
	TypeName enumName;
	size_t size;
};

在我们的结构体中,除了保存类型的大小之外,我们还保存了类型的可读字符串名称。

第一个是类型名,第二个枚举允许我们去更加轻松地决定我们查看的类型是什么。

一旦完成,我们可以制作一个宏,允许我们快速地去实例化新的类型。

#define DEFINE_TYPE(TYPE)\
template<>\
Type* GetType<TYPE>(){\
static Type type;\
	type.stringName = #TYPE;\
	type.size = sizeof(TYPE);\
	type.enumName = TypeName::TYPE;\
	return &type;\
}\

比如,展开int8_t。

template<>
Type* GetType<int8_t>(){
	static Type type;
	type.stringNmae = "int8_t";
	type.size = sizeof(int8_t);
	type.enumName = TypeName::int8_t;;
	return &type;
}

用来一些图元类型:

DEFINE_TYPE(int8_t)
DEFINE_TYPE(int16_t)
DEFINE_TYPE(int32_t)
DEFINE_TYPE(uint8_t)
DEFINE_TYPE(uint16_t)
DEFINE_TYPE(uint32_t)

目前我们有一些类型被定义,我们可以创建另一个结构体对于成员变量,对于那些我们需要在运行时访问的metadata。

struct Field
{
	Type* type;
	std::string name;
	size_t offset;
};

//MAX_NUMBER_OF_FILEDS is arbitrarily large
struct Class {
  std::array<Field, MAX_NUMBER_OF_FIELDS> fields;
};

所以,Class类,是Field的数组。

并且一些宏去快速地实例化被要求的数据:

#define BEGIN_ATTRIBUTES_FOR(CLASS)  \
template<> \
Class* GetClass<CLASS>() { \
  using ClassType = CLASS; \
  static Class localClass; \
  enum { BASE = __COUNTER__ }; \

#define DEFINE_MEMBER(NAME)  \
  enum { NAME##Index = __COUNTER__ - BASE - 1}; \
  localClass.fields[NAME##Index].type = GetType<decltype(ClassType::NAME)>();\
  localClass.fields[NAME##Index].name = { #NAME };  \
  localClass.fields[NAME##Index].offset = offsetof(ClassType, NAME);\

#define END_ATTRIBUTES \
  return &localClass; \
}\

__COUNTER__宏创建了字面值从零开始,在编译期间,并且每次它出现在一个文件中,它会被替换为它之前的值加+1。会被重置为0,对于每个新的类型被定义在一个文件中。

// Struct to reflect
struct TestStruct {
	int32_t field1;
	int16_t field2;
	int8_t field3;
	uint32_t field4;
	uint16_t field5;
	uint8_t field6;
};

// Reflection macro usage
BEGIN_ATTRIBUTES_FOR(TestStruct)
DEFINE_MEMBER(field1);
DEFINE_MEMBER(field2);
DEFINE_MEMBER(field3);
DEFINE_MEMBER(field4);
DEFINE_MEMBER(field5);
DEFINE_MEMBER(field6);
END_ATTRIBUTES
//展开TestStruct
template<>
Class* GetClass<TestStruct>() {
  using ClassType = TestStruct;//ClassType就是被反射的类
  static Class localClass;
  enum { BASE = 0 };
  
  enum { field1Index = 0};
  localClass.fields[field1].type = GetType<decltype(TestStruct::field1)>();
  localClass.fields[field1].name = { "field1" }; 
  localClass.fields[field1].offset = offsetof(TestStruct, field1);
  
  enum { filed2Index = 1};
  localClass.fields[field2].type = GetType<decltype(TestStruct::field2)>();//成员的type
  localClass.fields[field2].name = { "field2" }; 
  localClass.fields[field2].offset = offsetof(TestStruct, field2);

TestStruct成功地定义了一个模板特例化,对于一个函数。

目前,我们需要去创建另一个模板函数,可以使用我们创建的数据,去创建一个JSON字符串。为了这样,对于任何的类型,我们可以制作一些通用的语句,这些语句需要offsetof()宏的结果去寻找在内存中的位置,当数据被存储的时候。

template<typename T>
std::string SerializeObject(T& arg) {
  const Class* objectInfo = GetClass<T>();//获取相关的Class类
  rapidjson::Document document;
  rapidjson::Value key; 
  rapidjson::Value value; 

  document.SetObject(); 

  for (const auto& field : objectInfo->fields) {//遍历字段
    if (field.type == nullptr) break;

    key.SetString(field.name.c_str(), field.name.size(), document.GetAllocator());//成员变量名
    int8_t* source = reinterpret_cast<int8_t*>(&arg) + field.offset;
//获取变量在内存中的位置
    switch (field.type->enumName) {//遍历类型
      case TypeName::int8_t:
      case TypeName::int16_t:
      case TypeName::int32_t: {
        int32_t destination = 0;
        memcpy(&destination, source, field.type->size);
        value.SetInt(destination);
        break;
      }

      case TypeName::uint8_t:
      case TypeName::uint16_t:
      case TypeName::uint32_t: {
        uint32_t destination = 0; 
        memcpy(&destination, source, field.type->size);
        value.SetUint(destination);
        break;
      }

      default:
        assert(false);
        break;
      }
    
    document.AddMember(key, value, document.GetAllocator());
  }

	//写入到buffer里面
  rapidjson::StringBuffer buffer;
  rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
  document.Accept(writer);

  return buffer.GetString();
}

为了去访问我们的数据,我们必须找到在内存中我们数据确切的位置。

int8_t* source = reinterpret_cast<int8_t*>(&arg) + field.offset;

reinterpret_cast总是被转换成1字节的类型。

((base address + offsetof()))代替(base address + sizeof(arg) + offsetof())

Deserialization

对于反序列化,过程是有些不同,但是想法是一致的。
相对于返回一个字符串,我们返回一个以JSON字符串作为函数参数的反射类型。

template<typename T>
T DeserializeObject(const std::string& json) {
  const Class* objectInfo = GetClass<T>();
  T result; 
  rapidjson::Document document; 
  document.Parse(json.c_str());

  for (const auto& field : objectInfo->fields) {
    if (field.type == nullptr) break;
    if (document.HasMember(field.name.c_str()) && 
      (document[field.name.c_str()].IsInt() || document[field.name.c_str()].IsUint())) {
      auto* destination = reinterpret_cast<int8_t*>(&result) + field.offset;
      auto source = document[field.name.c_str()].GetInt(); 

      memcpy(destination, &source, field.type->size);
    }
  }

  return result; 
}

参考资料1
参考资料2