C++学习 优雅的实现对象到文件的序列化/反序列化 关键字serialize

需要使用到序列化场景的需求

  • 在写代码的过程中,经常会需要把代码层面的对象数据保存到文件,而这些数据会以各种格式存储.例如: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);  //  反序列化
//  POD
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())>, 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); }
}

//  POD
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())>, 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机制,可保证类型安全又具备可扩展性.
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Boost 库提供了非常强大的序列化反序列化功能,可以用来将对象转换为二进制数据,以及从二进制数据还原为对象。以下是一个简单的示例代码,展示了如何使用 Boost 库进行序列化反序列化: ```cpp #include <iostream> #include <fstream> #include <boost/archive/text_oarchive.hpp> #include <boost/archive/text_iarchive.hpp> class MyClass { public: int x; std::string name; // 序列化函数 template<class Archive> void serialize(Archive & ar, const unsigned int version) { ar & x; ar & name; } }; int main() { // 创建对象 MyClass obj; obj.x = 42; obj.name = "Boost"; // 序列化对象文件 std::ofstream ofs("data.txt"); boost::archive::text_oarchive oa(ofs); oa << obj; ofs.close(); // 从文件反序列化对象 MyClass restoredObj; std::ifstream ifs("data.txt"); boost::archive::text_iarchive ia(ifs); ia >> restoredObj; ifs.close(); // 输出反序列化后的对象内容 std::cout << "Restored Object: " << std::endl; std::cout << "x: " << restoredObj.x << std::endl; std::cout << "name: " << restoredObj.name << std::endl; return 0; } ``` 在上述代码中,我们定义了一个名为 `MyClass` 的类,并在其中添加了 `serialize` 函数,该函数用于定义对象序列化方式。我们使用 Boost 库中的 `text_oarchive` 类将对象序列化文件中,并使用 `text_iarchive` 类从文件反序列化对象。最后,我们输出反序列化后的对象内容。 请确保在编译时链接了 Boost 库,并将其头文件路径添加到编译器的包含路径中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值