需求


.  在写代码的过程中,经常会需要把代码层面的对象数据保存到文件,而这些数据会以各种格式存储.例如:json,xml,二进制等等.最近恰好就需要把对象以二进制存储到硬盘.这是一个很简单的需求,相比json,xml格式,二进制是直接把字节copy到硬盘,没有中间商赚差价,所以这实现起来相对容易.

实现
struct Vec3 { float x; float y; float z; }
.  上面是一个简单的三维向量结构体,如何把它序列化到文件呢?
Vec3 v; v.x = 1.0f; v.y = 2.0f; v.z = 3.0f; os.write((const char *)&v,
sizeof(Vec3));

.  上述是序列化Vec3对象数据到文件的代码,非常直接.它的内存布局是3个浮点型变量紧凑排列,要把它存储到硬盘,只要从头到尾按字节拷贝即可.但是,在实际开发中,要序列化的对象不可能全部都是内存紧凑排列的,例如STL容器.
std::vector<Vec3> vec;

.  如果将容器变量从头到尾拷贝到文件,必然会出现错误.因为容器内部通过一个指针来访问存储的对象,而直接拷贝这个容器,只会把指针拷贝,指针指向的数据却丢失了.但是,容器提供了一个可以直接访问指针指向数据的接口,我们可以通过这个接口得到数据然后直接拷贝.
os.write((const char *)&vec, vec.size() * sizeof(Vec3)); // 错误, 仅拷贝指针
os.write((const char *)vec.data(), vec.size() * sizeof(Vec3)); // 正确, 数据被完全拷贝
.  通过这个方法就可以得到正确的拷贝结果了.通常,好的做法是将序列化和反序列化封装成接口,以便于使用,如何封装接口,就是这篇文章的主题.

.  从上述两个例子可以发现,对于单体对象和数组对象,编写的代码是不一样的,单体对象直接拷贝,数组对象需要通过 .data()
取得数据地址再进行拷贝.而考虑到还有嵌套数组对象std::vector<std::vector<Vec3>>.对于嵌套数组序列化的代码可能如下:
std::vector<std::vector<Vec3>> vec2; for (auto & vec: vec2) { os.write((const
char *)vec.data(), vec.size() * sizeof(Vec3)); }
.  可以发现,对嵌套数组对象的序列化代码跟上述2种对象又不一样,考虑到还有N层嵌套的数组对象.此外,在C++中有一个可平凡复制的概念
,通俗的说,就是可以直接按字节拷贝的结构称之为可平凡复制,上述的Vec3
则是一个可平凡复制结构,而STL容器则不是可平凡复制结构,除此之外还有更多不可平凡复制且非容器的结构,故此,如果要封装接口,除了区分单体对象和数组对象,还要区分可平凡复制和不可平凡复制.
void Serialize(std::ostream & os, const Type & val); // 序列化 void
Deserialize(std::istream & is, Type & val); // 反序列化
.  上面是比较理想的接口原型,序列化/反序列化各一个接口,脑补一下,这两个接口的实现应该是怎样的?最直接的实现是对每一种类型重载一个定义,例如:
// string void Serialize(std::ostream & os, const std::string & val) {
os.write(str.data(), str.size()); } // vector<int> void Serialize(std::ostream
& os, const std::vector<int> & val) { os.write(str.data(), str.size() *
sizeof(int)); } // vector<string> void Serialize(std::ostream & os, const
std::vector<std::string> & val) { for (auto & str: val) { Serialize(os, str); }
} // 接口调用 std::string str; std::vector<int> vecint; std::vector<std::string>
vecstr; Serialize(os, str); Serialize(os, vecint); Serialize(os, vecstr);

.  从上面可以看出,接口统一,使用方便.但是对每一种类型都重载,要写的代码实在太多了,万一要序列化一个多层嵌套数组,会写的怀疑人生.借助C++强大的语言特性,这一切都可以一步到位.
// 可平凡复制 template <class T, typename
std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0> void
Serialize(std::ostream & os, const T & val) { os.write((const char *)&val,
sizeof(T)); } // 容器 template <class T, typename std::enable_if_t<
std::is_same_v<typename T::iterator, decltype(std::declval<T>().begin())> &&
std::is_same_v<typename T::iterator, decltype(std::declval<T>().end())> &&
std::is_trivially_copyable_v<typename T::value_type>, int> N = 0> void
Serialize(std::ostream & os, const T & val) { unsigned int size = val.size();
os.write((const char *)&size, sizeof(size)); os.write((const char *)val.data(),
size * sizeof(typename T::value_type)); } template <class T, typename
std::enable_if_t< std::is_same_v<typename T::iterator,
decltype(std::declval<T>().begin())> && std::is_same_v<typename T::iterator,
decltype(std::declval<T>().end())> && !std::is_trivially_copyable_v<typename
T::value_type>, int> N = 0> void Serialize(std::ostream & os, const T & val) {
unsigned int size = val.size(); os.write((const char *)&size, sizeof(size));
for (auto & v : val) { Serialize(os, v); } } // 可平凡复制 template <class T,
typename std::enable_if_t<std::is_trivially_copyable_v<T>, int> N = 0> void
Deserialize(std::istream & is, T & val) { is.read((char *)&val, sizeof(T)); }
// 容器 template <class T, typename std::enable_if_t< std::is_same_v<typename
T::iterator, decltype(std::declval<T>().begin())> && std::is_same_v<typename
T::iterator, decltype(std::declval<T>().end())> &&
std::is_trivially_copyable_v<typename T::value_type>, int> N = 0> void
Deserialize(std::istream & is, T & val) { unsigned int size = 0; is.read((char
*)&size, sizeof(unsigned int)); val.resize(size); is.read((char *)val.data(),
size * sizeof(typename T::value_type)); } template <class T, typename
std::enable_if_t< std::is_same_v<typename T::iterator,
decltype(std::declval<T>().begin())> && std::is_same_v<typename T::iterator,
decltype(std::declval<T>().end())> && !std::is_trivially_copyable_v<typename
T::value_type>, int> N = 0> void Deserialize(std::istream & is, T & val) {
unsigned int size = 0; is.read((char *)&size, sizeof(unsigned int));
val.resize(size); for (auto & v : val) { Deserialize(is, v); } }
.  以上实现可序列化任意可平凡拷贝结构,并且也可序列化任意嵌套层数的STL风格数组.而对于不可平凡复制
结构,只需要针对该结构重载即可.借助C++强大的类型推导机制和SFINEA机制,可保证类型安全又具备可扩展性.

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信