1.什么是结构体序列化和反序列化
序列化:就是将对象转化成字节序列的过程。
反序列化:就是讲字节序列转化成对象的过程。
对象序列化成的字节序列会包含对象的类型信息、对象的数据等,说白了就是包含了描述这个对象的所有信息,能根据这些信息“复刻”出一个和原来一模一样的对象。
简单的来说结构体序列化就是将这个结构体里的数据转成一段通用的数据,另一个同类型结构体拿到这段数据后可以反序列化拿到每个结构体成员的值。
#include <iostream>
#include <memory>
using namespace std;
struct A
{
int a;
int b;
};
void test(const char* s, int len)
{
A b;
memcpy((char *)&b, s, len);
cout<<b.a<<" "<<b.b;
}
int main(int argc, char *argv[])
{
A a;
a.a = 20;
a.b = 30;
test((const char *)&a, sizeof(a));
return 0;
}
上述代码中将a的数据拷贝到b,当然像A这样简单的结构体直接=就能进行拷贝了。但是如果在程序中多处需要传递该结构体,就需要考虑传参过程中使用更通用的数据类型了。在上述代码中可以把数据先拷贝到c++的String容器中,然后再进行传递,在使用时再将String中的数据拷贝给结构体。这样就完成了一次序列化和反序列化。
2.为什么要结构体序列化和反序列化
2.1数据持久化
通常我们创建的结构体实例的数据都保存再堆区或者栈区,栈区数据程序结束后会被自动释放,而为了避免内存泄漏,我们必须要手动释放掉申请的堆区内存。那么如果想要在新的程序中得到之前程序的结构体数据,就需要把数据序列化成字节序列,然后将它存入硬盘,再新的程序中用该字节序列就能反序列化出一个一摸一样的结构体实例。
2.2避免内存对齐导致数据拷贝失败
#include <iostream>
using namespace std;
#pragma pack (4) //指定按多少字节进行内存对齐
struct A
{
int a;
double b;
int c;
};
int main()
{
cout<<sizeof(A);
return 0;
}
指定变量强制按4字节对齐,则A的大小是16
#include <iostream>
using namespace std;
#pragma pack (8) //指定按多少字节进行内存对齐
struct A
{
int a;
double b;
int c;
};
int main(int argc, char *argv[])
{
cout<<sizeof(A);
return 0;
}
指定变量强制按8字节对齐,则A的大小变为24。
为什么会出现这样的现象呢?这里面就涉及到内存对齐的问题。 本文不会重点介绍内存对齐原理,具体请看以下文章。 一文轻松理解内存对齐 - 知乎 (zhihu.com)
如果在网络传输中,程序所在环境的内存对齐规则不一致的化,就会出现计算出的结构体所占内存大小不一致的情况。这样直接对整个结构体进行内存拷贝就会导致多拷贝或少拷贝几个字节,这实际上是很危险的,很容易导致内存崩溃。
解决办法可以参考网络传输中常见的Json序列化和XM序列化,这两者都是将对象序列化成字符串然后进行传输,在反序列化时再按照一定规则去依次解析出相应数据。虽然转成字符串有很好的可读性,但是确大大的增加了传输数据的长度。实际上我们自己可以将每一个结构体成员变量依次拷贝到一段缓存去,然后再转换成其他通用数据结构进行传输。
#include <iostream>
#include <memory>
#include <stdlib.h>
#include <cstdio>
#include <cstring>
#include<string>
using namespace std;
class CSeArchive
{
public:
CSeArchive(int a_buf_size = 1024 * 5 )
{
buf = new char[a_buf_size];
memset(buf,0x00,a_buf_size);
bufptr = buf;
buf_size = a_buf_size;
data_size = 0;
}
CSeArchive( const char *a_buf, int a_buf_size)
{
buf = new char[1024 * 5];
memcpy(buf,a_buf, a_buf_size);
bufptr = buf;
buf_size = 1024 * 5;
data_size = a_buf_size;
}
~CSeArchive()
{
if(sizeof(buf) != 0)
{
delete[] buf;
buf = nullptr;
bufptr = nullptr;
}
}
char* get_buf(std::string &strData) //得到缓存指针
{
strData = std::string(buf,data_size);
return buf;
}
inline CSeArchive& operator<<(int i) //往缓存存中写入int数据
{
memcpy(bufptr,(char*)&i,sizeof(i));
bufptr += sizeof(i);
data_size += sizeof(i);
return *this;
}
inline CSeArchive& operator<<(double i) //往缓存中double数据
{
memcpy(bufptr,(char*)&i,sizeof(i));
bufptr += sizeof(i);
data_size += sizeof(i);
return *this;
}
inline CSeArchive& operator>>(int& i) //从缓存中读出int数据
{
memcpy((char *)&i, bufptr, sizeof(i));
bufptr += sizeof(i);
return *this;
};
inline CSeArchive& operator>>(double &i) //从缓存中读出double数据
{
memcpy((char *)&i, bufptr, sizeof(i));
bufptr += sizeof(i);
return *this;
}
private:
char *buf, *bufptr;
int buf_size, data_size;
};
struct A
{
int a;
double b;
int c;
string Serialize()
{
CSeArchive cs;
cs<<a<<b<<c;
string ret;
cs.get_buf(ret);
return ret;
}
void UnSerialize(string s)
{
CSeArchive cs(s.c_str(),s.size());
cs>>a>>b>>c;
}
};
int main()
{
A a;
a.a = 10;
a.b = 20.5;
a.c = 30;
A b;
string s = a.Serialize();
b.UnSerialize(s);
cout<<b.a<<" "<<b.b<<" "<<b.c;
return 0;
}
2.3进行复杂的结构体的拷贝
当结构体中含有对象或容器时是无法直接进行内存拷贝的,这时我们就可以通过序列化和反序列化的手段进行拷贝。实现方法也很简单只需重载<<运算符将数据写入缓存,重载>>运算符将数据读出即可。
2.3.1支持数组
template<typename T, size_t N> //写入数组
inline CSeArchive& operator<<(T(&m)[N]){
return wr_array(m, N);
}template<typename T>
inline CSeArchive& wr_array(T arData[], uint32 uCount) //写数组
{
for(int i = 0;i < uCount; i++)
{
memcpy(bufptr,arData+i,sizeof(arData[0]));
bufptr += sizeof(arData[0]);
data_size += sizeof(arData[0]);
}
return *this;
}
template<typename T, size_t N> //读出数组
inline CSeArchive& operator>>(T(&m)[N]) {return rd_array(m, N);}
template<typename T>
inline CSeArchive& rd_array(T arData[], uint32 uCount){
for(int i = 0;i < uCount; i++)
{
memcpy(arData + i,bufptr, sizeof(arData[0]));
bufptr += sizeof(arData[0]);
}
return *this;}
2.3.2支持string容器
int CSeArchive::getStrlen(char *buf) //找到当前指针到下一个'\0'之间的距离
{
if(buf == nullptr)
return 0;
int count = 0;
while(*buf != '\0' && buf != nullptr && count < data_size)
{
buf++;
count++;
}
return count;
}CSeArchive& operator <<(const std::string& strValue) //重载运算符(写入string)
{
memcpy(bufptr,strValue.data(),strValue.size());
bufptr += strValue.size()+1;
data_size += strValue.size()+1;
return *this;
}CSeArchive& operator >> (std::string& strValue) //重载运算符(读出string)
{
int len = getStrlen(bufptr);
strValue = std::string(bufptr,len);
bufptr += len + 1;
return *this;
}
2.3.3支持vector容器(其他容器可举一反三)
//写入vector<string>类型
inline CSeArchive& operator<<(vector<string> &vtData) {return wr_array(vtData);}
inline CSeArchive& wr_array(vector<string> &vtData)
{
(*this)<<(int)vtData.size();for(int i = 0;i < vtData.size();i++)
(*this)<<vtData[i];
return *this;
}
//读出vector<string>类型
inline CSeArchive& operator>>(vector<string> &vtData) {return rd_array(vtData);}
inline CSeArchive& rd_array(vector<string> &vtData)
{
int size = 0;
(*this)>>size;
for(int i = 0;i < size;i++)
{
string data = "";
(*this)>> data;
vtData.push_back(data);
}
return *this;
}
2.3.4支持对象
template<typename T, std::enable_if_t < !std::is_enum<T>::value > * = nullptr >
inline CSeArchive& operator<<(T tParam) //传入对象
{
std::string tString = tParam.Serialize(); //对象进行序列化并存入缓存
wr_array((char*)tString.c_str(), tString.length());
return *this;
}
template<typename T, std::enable_if_t < !std::is_enum<T>::value > * = nullptr >
inline CSeArchive& operator>>(T& tParam) //传出对象(初始化)
{
tParam.T::UnSerialize(*this);
return *this;
}
3.具体实现
具体实现代码请自取。https://gitee.com/Lanlvzzz/serialize.git