序列化是什么,反射是什么?
成熟的序列化工具以及反射实现看起来好复杂,有没有简单的示例代码可以让大家体会序列化和反射的简单本质。
序列化
序列化是一种将数据结构或对象状态转换为比特流格式的行为,这些比特流可以在硬盘上存储,或者通过网络传输,之后再恢复为原始格式。
可以不用特定的序列化代码,直接将内存数据保存/发送吗?
譬如:
void NaivelySendPersonStatus(int inSocket, const PersonStatus* inPersonStatus)
{
send(inSocket, reinterpret_cast<const char*>(inPersonStatus), sizeof(inPersonStatus), 0);
}
void NaivelyRecvPersonStatus(int inSocket, PersonStatus* outPersonStatus)
{
recv(inSocket, reinterpret_cast<char*>(outPersonStatus), sizeof(PersonStatus), 0);
}
对于示例的PersonStatus简单类,上面的代码大多数情况是能work的。或者说对于POD类,直接将内存拷贝保存/发送可以凑合着用。
但用于网络传输,或者存储的跨平台使用,即便是POD数据,做简单的内存拷贝是不够的,需要处理不同平台大小端不一致的问题。
//ByteSwap.h
#pragma once
#include<stdint.h>
static void Swap(uint8_t &a, uint8_t &b)
{
uint8_t tmp = a;
a = b;
b = tmp;
}
static void ByteSwap(void* val, int32_t len)
{
uint8_t* ptr = (uint8_t*)val;
int32_t top = len - 1;
int32_t bottom = 0;
while (bottom < top)
{
Swap(ptr[top--], ptr[bottom++]);
}
}
template<typename T>
static T ByteSwap(T val)
{
static_assert(std::is_fundamental<T>::value, "ByteSwap only supports primitive data types.");
T result = val;
ByteSwap(&result, sizeof(T));
return result;
}
小规模的POD数据简单序列化,处理好大小端问题就好了。
对于真正的序列化工具,还需要处理继承的虚函数指针问题、成员变量的指针问题、以及vector<T>等特殊数据类型的序列化问题。 序列化工具还需要考虑性能和易用性,一般还会有数据压缩处理以缩减流量,以及使用TLV编码以满足扩展性。可参考Protobuf的实现。
反射
一些语言如C#和Java有内置的反射系统,允许运行时访问类结构。通过反射系统可以更轻松实现序列化。 C++并没有反射系统,我们可以简单搭建一个反射系统。
反射系统的简单实现
//DataReflection.h
#pragma once
#include<vector>
#include <initializer_list>
using namespace std;
#define OffsetOf(c, mv) ((size_t) & (static_cast<c*>(nullptr)->mv))
enum EnmPrimitiveType
{
EPT_INT32,
EPT_INT16,
EPT_INT8,
EPT_CharAry, //定长char[], 符合POD
EPT_Float,
EPT_Double,
};
class MemberVariable
{
public:
MemberVariable(string name, EnmPrimitiveType ePrimType, int offset, int size)
: mName(name), mEPrimType(ePrimType), mOffset(offset), mSize(size) {}
EnmPrimitiveType GetPrimitiveType() const { return mEPrimType; }
uint32_t GetOffset() const { return mOffset; }
uint32_t GetSize() const { return mSize; }
private:
string mName;
EnmPrimitiveType mEPrimType;
uint32_t mOffset;
uint32_t mSize;
};
class DataType
{
public:
DataType(initializer_list<MemberVariable> inMVs) : mMemberVariables(inMVs) {}
const vector<MemberVariable>& GetMemberVariables() const { return mMemberVariables; }
private:
vector<MemberVariable> mMemberVariables;
};
反射如何用?
我们自定义一个POD class:PersonStatus,在其中添加static DataType* sDataType记录类结构。
然后,可以统一地根据DataType进行序列化。
//PersonStatus.h
#pragma once
#include "DataReflection.h"
class PersonStatus
{
public:
char mName[128];
int mID;
double mHeight;
int mChildCount;
float mHealth;
public:
static DataType* sDataType;
static void InitDataType()
{
sDataType = new DataType(
{
MemberVariable("mName", EnmPrimitiveType::EPT_CharAry, OffsetOf(PersonStatus, mName), 128),
MemberVariable("mID", EnmPrimitiveType::EPT_INT32, OffsetOf(PersonStatus, mID), sizeof(int)),
MemberVariable("mHeight", EnmPrimitiveType::EPT_Double, OffsetOf(PersonStatus, mHeight), sizeof(double)),
MemberVariable("mChildCount", EnmPrimitiveType::EPT_INT32, OffsetOf(PersonStatus, mChildCount), sizeof(int)),
MemberVariable("mHealth", EnmPrimitiveType::EPT_Float, OffsetOf(PersonStatus, mHealth), sizeof(float))
});
}
};
static void Serialize(MemoryStream* inMemoryStream, const DataType* inDataType, uint8_t* inData)
{
for (auto& mv : inDataType->GetMemberVariables())
{
uint8_t* mvData = inData + mv.GetOffset();
switch (mv.GetPrimitiveType())
{
case EnmPrimitiveType::EPT_INT32:
{
inMemoryStream->Serialize((int*)mvData, mv.GetSize());
break;
}
case EnmPrimitiveType::EPT_Float:
{
inMemoryStream->Serialize((float*)mvData, mv.GetSize());
break;
}
case EnmPrimitiveType::EPT_Double:
{
inMemoryStream->Serialize((double*)mvData, mv.GetSize());
break;
}
case EnmPrimitiveType::EPT_CharAry:
{
inMemoryStream->Serialize((char*)mvData, mv.GetSize());
break;
}
default:
break;
}
}
}
添加有DataType的类可以方便地进行序列化。 当然,PersonStatus里的DataType是手动写的,若大规模使用可考虑用工具自动生成。 可参考UE4的反射的实现。