序列化是编程中一个非常实用的技术,它能够将对象转换为可以存储或传输的格式,而反序列化则是将这种格式重新转换为对象的过程。今天,我将分享如何利用C++的模板元编程技术来实现一个简单而强大的序列化库。
一、什么是序列化?
想象一下,你需要将一个C++对象保存到文件中,或者通过网络发送给另一台计算机。序列化就是将对象转换为字节流的过程,而反序列化则是将这些字节流重新构建为原始对象。
本文实现的序列化库支持以下数据类型:
- 基本类型:char、short、int、long、float、double以及无符号整数;
- 字符串:std::string;
- 向量:std::vector,支持vector<基本类型>、vector<字符串>及vector<struct>;
- 嵌套结构:支持复杂的嵌套数据结构。
二、技术原理
2.1 模板元编程基础
模板元编程(Template Metaprogramming,TMP)是C++中一种在编译期执行计算的技术。它利用模板特化、递归实例化等机制,在编译期间生成代码,从而实现类型安全的通用编程。
在我们的序列化库中,模板元编程主要体现在以下几个方面:
- 编译期类型判断:使用
std::is_class_v在编译期判断类型是否为类类型。 - 模板特化:为不同类型提供特定的序列化实现。
- 递归模板:处理可变参数和嵌套结构。
2.2 类型分发机制
类型分发是序列化库的核心技术,它根据不同的类型选择不同的序列化策略:
template <bool cond, typename T>
class IF {
// 类类型的序列化策略
};
template <typename T>
class IF<false, T> {
// 基本类型的序列化策略
};
template <typename T>
inline size_t valsize(const T& val) {
return IF<std::is_class_v<T>, T>::_val_size(val);
}
这种设计实现了:
- 零运行时开销:所有类型判断在编译期完成。
- 类型安全:错误的类型使用会在编译期报错。
- 可扩展性:添加新类型只需提供相应的特化版本。
2.3 序列化协议设计
2.3.1 数据包格式
我们的序列化数据包采用简单的结构:
----------------------
| 包长度 | 数据 |
|---------------------|
| int | char[] |
----------------------
这种设计具有以下优点:
- 快速长度检查:通过包长度可以快速验证数据完整性。
- 内存预分配:知道总长度后可以一次性分配足够内存。
- 错误检测:反序列化时可以通过长度验证数据有效性。
2.3.2 类型编码规则
| 数据类型 | 符号表示 | 排列方式(len(x)为unsigned int) |
| 基本类型 | T | T |
| 字符串 | S | len(S) + S |
| 基本类型向量 | V<T> | len(V) + T1 + T2 + ... + Tn |
| 字符串向量 | V<S> | len(V) + len(S1) + S1 + len(S2) + S2 + ... + len(Sn) + Sn |
这种编码规则确保了:
- 自描述性:数据本身包含了解析所需的信息。
- 可扩展性:支持嵌套和复杂数据结构。
- 平台无关性:使用固定字节序和明确的大小表示。
三、核心代码实现
// Serialization.hpp
/* 序列化说明
* 1.包组成:
* -----------------------
* | 包长度 | 数据 |
* |-----------------------|
* | int | char[] |
* -----------------------
* 2.数据组成
* -------------------------------------------------------------------------------------
* | 类型 | 符号表示 | 排列方式,其中len(x)的类型为unsigned int |
* |-------------------------------------------------------------------------------------|
* | 基本类型 | T | T |
* | 字符串 | S | len(S) + S |
* | 基本类型向量 | V<T> | len(V) + T1 + T2 + T3 + ... + Tn |
* | 字符串向量 | V<S> | len(V) + len(S1) + S1 + len(S2) + S2 + ... + len(Sn) + Sn |
* -------------------------------------------------------------------------------------
*/
#ifndef SERIALLIZATION_H_
#define SERIALLIZATION_H_
#include <string>
#include <vector>
#define DEFINE_PACK(...) \
public: \
size_t pack_size() const { \
return sizeof(int) + Ser::all_size(__VA_ARGS__); \
} \
std::string to_string() const { \
const size_t _count = sizeof(int) + Ser::all_size(__VA_ARGS__); \
std::string _result; \
_result.reserve(_count); \
Ser::push(_result, (int) _count); \
Ser::pack(_result, __VA_ARGS__); \
return _result; \
} \
bool from_string(const char* const _buf, const int _len) { \
if (_len < sizeof(int)) \
return false; \
const char* _ptr = _buf; \
int _count = 0; \
Ser::take(_count, _ptr); \
if (_count != _len) \
return false; \
Ser::unpack(_ptr, __VA_ARGS__); \
return true; \
} \
bool from_string(const std::string& _buf) { \
return from_string(_buf.data(), _buf.size()); \
}
namespace Ser {
template <bool cond, typename T>
class IF {
public:
static inline size_t _val_size(const T& val) {
return val.pack_size();
}
static inline void _push(std::string& buf, const T& val) {
buf.append(val.to_string());
}
static inline void _take(T& val, const char*& buf) {
const int count = *(reinterpret_cast<const int*>(buf));
val.from_string(buf, count);
buf += count;
}
};
template <typename T>
class IF<false, T> {
public:
static inline size_t _val_size(const T& val) {
return sizeof(T);
}
static inline void _push(std::string& buf, const T& val) {
const char* ptr = reinterpret_cast<const char*>(&val);
buf.append(ptr, sizeof(T));
}
static inline void _take(T& val, const char*& buf) {
val = *(reinterpret_cast<const T*>(buf));
buf += sizeof(T);
}
};
/* 计算单个元素序列化后的长度 */
template <typename T>
inline size_t valsize(const T& val) {
return IF<std::is_class_v<T>, T>::_val_size(val);
}
template <>
inline size_t valsize<std::string>(const std::string& val) {
return sizeof(unsigned int) + val.size();
}
template <typename T>
size_t valsize(const std::vector<T>& val) {
size_t len = sizeof(unsigned int);
for (size_t i = 0; i < val.size(); ++i) {
len += valsize(val[i]);
}
return len;
}
/* 序列化单个元素 */
template <typename T>
inline void push(std::string& buf, const T& val) {
IF<std::is_class_v<T>, T>::_push(buf, val);
}
template <>
inline void push<std::string>(std::string& buf, const std::string& val) {
const unsigned int len = static_cast<unsigned int>(val.size());
buf.append(reinterpret_cast<const char*>(&len), sizeof(unsigned int));
buf.append(val);
}
template <typename T>
void push(std::string& buf, const std::vector<T>& val) {
const unsigned int len = static_cast<unsigned int>(val.size());
push(buf, len);
for (unsigned int i = 0; i < len; ++i) {
push(buf, val[i]);
}
}
/* 反序列化单个元素 */
template <typename T>
inline void take(T& val, const char*& buf) {
IF<std::is_class_v<T>, T>::_take(val, buf);
}
template <>
inline void take<std::string>(std::string& val, const char*& buf) {
unsigned int len = 0;
take(len, buf);
val.clear();
val.append(buf, len);
buf += len;
}
template <typename T>
void take(std::vector<T>& val, const char*& buf) {
unsigned int len = 0;
take(len, buf);
val.resize(len);
for (unsigned int i = 0; i < len; ++i) {
take(val[i], buf);
}
}
/* 计算不定参序列化后总长度 */
template <typename T>
size_t all_size(const T& elem) {
return valsize(elem);
}
template <typename T, typename... Args>
size_t all_size(const T& elem, const Args&... args) {
return valsize(elem) + all_size(args...);
}
/* 不定参序列化 */
template <typename T>
void pack(std::string& buf, const T& elem) {
push(buf, elem);
}
template <typename T, typename... Args>
void pack(std::string& buf, const T& elem, const Args&... args) {
push(buf, elem);
pack(buf, args...);
}
/* 不定参反序列化 */
template <typename T>
void unpack(const char*& buf, T& elem) {
take(elem, buf);
}
template <typename T, typename... Args>
void unpack(const char*& buf, T& elem, Args&... args) {
take(elem, buf);
unpack(buf, args...);
}
} // namespace Ser
#endif /* SERIALLIZATION_HPP_ */
四、使用示例
4.1基本使用
#include "Serialization.hpp"
class Message {
public:
std::string type; // 类型
std::string msg; // 内容
int priority; // 优先级
long long id; // 序号
std::string ip; // IP
DEFINE_PACK(type, msg, priority, id, ip)
};
int main() {
Message m = { "type", "test", 2, 56, "192.168.0.2" };
// 序列化
std::string buf = m.to_string();
// 反序列化
Message res;
res.from_string(buf);
return 0;
}
4.2嵌套使用
#include "Serialization.hpp"
class Base {
public:
std::string name;
int id;
std::vector<double> values;
DEFINE_PACK(name, id, values)
};
class FullInfo {
public:
std::string name;
float val;
std::vector<std::vector<Base>> vec;
DEFINE_PACK(name, val, vec)
};
int main() {
FullInfo obj;
obj.name = "Hello";
obj.val = 3.14F;
obj.vec = {
{
{"elem 1", 1, {1.1, 1.2, 1.3, 1.4}},
{"elem 2", 2, {2.1, 2.2}},
{"elem 3", 3, {3.1, 3.2, 3.3}}
},
{
{"elem 4", 4, {4.1, 4.2, 4.3, 4.4}},
{"elem 5", 5, {5.1, 5.2}},
{"elem 6", 6, {6.1, 6.2, 6.3, 6.4, 6.5}}
}
};
// 序列化
const std::string buf = obj.to_string();
// 反序列化
FullInfo res{};
res.from_string(buf);
return 0;
}
这篇博客介绍了如何利用C++的模板元编程技术来实现序列化和反序列化功能,支持的基本类型包括char、short、int、long、float、double及无符号整数,同时支持std::string和std::vector,对于std::vector则支持存储基本类型和字符串。内容涵盖序列化的简介、具体实现以及通过多个使用示例展示了其基本和嵌套的用法。
984

被折叠的 条评论
为什么被折叠?



