序列化和反射

本文介绍了序列化和反射的基本概念,并提供了简单的C++示例代码。对于序列化,讨论了如何处理大小端问题以及简单的POD数据序列化。对于反射,展示了如何构建一个简单的反射系统,允许运行时访问类结构并进行序列化操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

序列化是什么,反射是什么?

成熟的序列化工具以及反射实现看起来好复杂,有没有简单的示例代码可以让大家体会序列化和反射的简单本质。

 

序列化

序列化是一种将数据结构或对象状态转换为比特流格式的行为,这些比特流可以在硬盘上存储,或者通过网络传输,之后再恢复为原始格式。

可以不用特定的序列化代码,直接将内存数据保存/发送吗?

譬如:

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的反射的实现。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值