主体类实现序列化和反序列化功能,用的模版元编程。通过重载<<达到流式编码效果。
代码里没有用using namespace,不习惯这种用法,习惯了显式namespace。
编码结果参考YaZi, 将变量编码为: 1字节(类型) + n字节(数据)。
对容器类编码为: 1字节(类型) + 5字节(长度,其中1字节代表类型,4字节代表长度) + n字节(数据)。
类型
类型通过枚举类表示
enum my_enum
{
UNKNOW = -1,
INT = 0,
INT64,
BOOL,
CHAR,
FLOAT,
DOUBLE,
CHARPTR,
STRING,
VECTOR,
DEQUE,
SET,
MULTISET,
MAP,
MULTIMAP,
LIST,
CUSTOM
};
序列化实现
template<class T>
Serialize& operator << (T data)
{
this->write(data);
this->obj_num++;
return *this;
}
可以看到调用的是write方法,在实现中对write方法做了一些重载,主要处理
1. 基本类型,int32,bool之类的,非指针类型。
// 基本数据类型
template<class T, class = std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>>
void write(T data)
{
this->writeType(data);
this->write((char*)&data, sizeof(data));
}
对基本类型处理直接转为char*指针,本质是将数据转为字节流传输,调用memcpy_s方法写入字节流。
void Serialize::write(const char* data, int len)
{
this->reserve(len);
int size = this->m_buf.size();
this->m_buf.resize(size + len);
memcpy_s(&this->m_buf[size], size+len, data, len);
}
其中std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>匹配基本数据类型,个人感觉std::enable_if_t主要在模版中提供一种筛选功能,具体可以看官网介绍。std::decay_t用来进行类型擦除,主要擦除const以及引用这些。
2. stl标准容器类型
下边是write的另外两个重载,用来处理stl标准容器类型
// 类list类型
template<class T>
std::enable_if_t<is_array_v<T>, void> write(const T& data)
{
this->writeType(data);
int len = data.size();
this->write(len);
for(auto iter = data.begin(); iter != data.end(); iter++)
{
this->write(*iter);
}
}
// k,v类型
template<class T>
std::enable_if_t<is_KV_v<T>, void> write(const T& data)
{
this->writeType(data);
int len = data.size();
this->write(len);
for(auto iter = data.begin(); iter != data.end(); iter++)
{
this->write(iter->first);
this->write(iter->second);
}
}
3.string类型
对string类型做单独处理,因为需要调用c_str将string转为char*
// std::string类型
template<class T>
std::enable_if_t<std::is_same_v<std::decay_t<T>, std::string>, void> write(T data)
{
this->writeType(data);
this->write(data.c_str());
}
4. char*类型
没有考虑对其他类型数组的处理,主要原因是除了char类型数组,其他数组没有明显的结束符,需要额外传递长度参数,建议直接使用stl标准库的list/vector。而char*类型一般表示字符串,有明显的'\0'结束符,因此直接对char*类型做了偏特化。这样所有类型只需要传递数据本身,在调用时没有差别。
void Serialize::write(const char* data)
{
int len = strlen(data) + 1;
this->write(len);
this->write(data, len);
}
5. 自定义类型处理
此处借鉴了YaZi大佬的思路,通过宏获取自定义类中需要序列化的属性,使用变长模版参数进行处理。唯一感觉难受的就是需要将该方法做成public,有点影响封装。
关于自定义类型,需要继承SerializeAble类并且调用宏SERIALIZEABLE将自定义类中需要序列化的字段写入。
void Serialize::write(SerializeAble& serialize_able)
{
serialize_able.serialize(*this);
}
#define SERIALIZEABLE(...) \
void serialize(zdsj::Serialize& stream) const override \
{ \
auto type_name = zdsj::CUSTOM; \
stream.write_args(type_name, __VA_ARGS__); \
} \
bool unSerialize(zdsj::Serialize& stream) override \
{ \
return stream.read_args(__VA_ARGS__); \
} \
template<class T, class ...Args>
void write_args(T& data, Args& ...args)
{
this->write(data);
this->write_args(args...);
}
void write_args()
{
}
class A : public zdsj::SerializeAble
{
public:
A()
{
}
void show()
{
std::cout << "a=" << a << " b=" << b << std::endl;
}
SERIALIZEABLE(a, b)
private:
int a = 2;
float b = 23.0;
};
序列化中对stl容器类型判断方法
这里借鉴了其他大佬的思路,通过模版匹配的方式判断
template<typename T,typename... Types>
struct IsContainerType
{
static const bool value = false;
static const my_enum type = zdsj::CUSTOM;
};
// Vector类型为true
template<typename T,typename... Types>
struct IsContainerType<std::vector<T, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::VECTOR;
};
// deque类型
template<typename T,typename... Types>
struct IsContainerType<std::deque<T, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::DEQUE;
};
// set类型
template<typename T,typename... Types>
struct IsContainerType<std::set<T, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::SET;
};
// multiset类型
template<typename T,typename... Types>
struct IsContainerType<std::multiset<T, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::MULTISET;
};
// map类型
template<typename K,typename V,typename... Types>
struct IsContainerType<std::map<K, V, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::MAP;
};
// multimap类型
template<typename K,typename V,typename... Types>
struct IsContainerType<std::multimap<K, V, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::MULTIMAP;
};
// list类型
template<typename T,typename... Types>
struct IsContainerType<std::list<T, Types...>>
{
static const bool value = true;
static const my_enum type = zdsj::LIST;
};
// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr bool is_container_v = IsContainerType<T, Types...>::value;
// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr my_enum is_container_t = IsContainerType<T, Types...>::type;
// 定义获取容器类型的模板
template<typename T,typename... Types>
constexpr bool is_array_v = IsContainerType<T, Types...>::type == zdsj::VECTOR ||
IsContainerType<T, Types...>::type == zdsj::SET ||
IsContainerType<T, Types...>::type == zdsj::MULTISET ||
IsContainerType<T, Types...>::type == zdsj::DEQUE ||
IsContainerType<T, Types...>::type == zdsj::LIST;
template<typename T,typename... Types>
constexpr bool is_KV_v = IsContainerType<T, Types...>::type == zdsj::MAP ||
IsContainerType<T, Types...>::type == zdsj::MULTIMAP;
反序列化
反序列化与序列化操作相反,代码已上传gitee
找到数据起始地址所在索引,将其转换为指针后直接解引用,提前加了判断,判断类型是否匹配,防止解引用时数据出错,其他数据格式同理:
// 基本数据类型
template<class T, class = std::enable_if_t<std::is_pod_v<std::decay_t<T>>, void>>
bool read(T& data)
{
auto read_type = my_enum(this->m_buf[this->m_pos]);
this->m_pos++;
if(this->checkType(data, read_type))
{
data = *(T*)(&this->m_buf[this->m_pos]);
this->m_pos += sizeof(T);
return true;
}else
{
return false;
}
}
由于反序列化实现的方式,在使用时需要反序列化顺序与序列化相同,否则容易出现类型不匹配导致反序列化失败的情况。
其实本质上不需要对类型进行编码,只要保证反序列化时与序列化顺序相同即可,但是那样因为缺少类型判断,如果顺序不同,容易出现数据读取错误的问题。
代码地址:Serialize: c++序列化