Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

struct_pack support user-defined type which is not in struct_pack type system #472

Closed
2 tasks done
uchenily opened this issue Oct 10, 2023 · 10 comments · Fixed by #514
Closed
2 tasks done

struct_pack support user-defined type which is not in struct_pack type system #472

uchenily opened this issue Oct 10, 2023 · 10 comments · Fixed by #514

Comments

@uchenily
Copy link

uchenily commented Oct 10, 2023

Search before asking

  • I searched the issues and found no similar issues.

What happened + What you expected to happen

咨询问题, 请问 struct_pack 是否能够实现非侵入式序列化第三方库.

比如下面这个例子, 通过自定义两个模板函数load/save, 使用boost
serialazation模块实现对第三方库 libntl 中的比较复杂类型 NTL::ZZ 的序列化, 而不用对这个库本身进行修改, 因此修改是非侵入式的.

#include "NTL/ZZ.h"
#include "struct_pack.hpp"

#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/serialization/vector.hpp>

namespace boost_pack {
template <typename T>
std::string serialize(const T &t) {
    std::stringstream ss;
    boost::archive::binary_oarchive out_archive(ss);
    try {
        out_archive << t;
        return ss.str();
    } catch (const boost::archive::archive_exception &e) {
        throw std::runtime_error(std::string(e.what()));
    }
}

template <typename T>
T deserialize(const std::string &s) {
    T res;
    std::istringstream iss(s);
    boost::archive::binary_iarchive in_archive(iss);
    try {
        in_archive >> res;
        return res;
    } catch (const boost::archive::archive_exception &e) {
        throw std::runtime_error(std::string(e.what()));
    }
}
} // namespace boost_pack

namespace boost {
namespace serialization {
// NTL::ZZ
template <class Archive>
inline void
save(Archive &ar, const NTL::ZZ &z, const unsigned int /* file_version */) {
    std::vector<unsigned char> buf(NTL::NumBytes(z));
    NTL::BytesFromZZ(buf.data(), z, static_cast<long>(buf.size()));
    ar &buf;
}

template <class Archive>
inline void
load(Archive &ar, NTL::ZZ &z, const unsigned int /* file_version */) {
    std::vector<unsigned char> buf;
    ar &buf;
    NTL::ZZFromBytes(z, buf.data(), static_cast<long>(buf.size()));
}

} // namespace serialization
} // namespace boost
BOOST_SERIALIZATION_SPLIT_FREE(NTL::ZZ)

int main() {
    NTL::ZZ zz(1000);

    auto buffer1 = boost_pack::serialize(zz);
    auto zz1 = boost_pack::deserialize<NTL::ZZ>(buffer1);

    // ???
    // auto buffer2 = struct_pack::serialize<std::string>(zz);
    // auto zz2 = struct_pack::deserialize<NTL::ZZ>(buffer2);
}

我想知道在 struct_pack 中应该怎么在不更改第三方库的前提下实现这样的需求? 或者有什么其他的解决方案

Reproduction way

Anything else

示例中的库以及头文件地址

https://github.com/libntl/ntl
https://github.com/libntl/ntl/blob/main/include/NTL/ZZ.h

Are you willing to submit a PR?

  • Yes I am willing to submit a PR!
@uchenily uchenily changed the title 非侵入式支持第三方库 struct_pack非侵入式支持第三方库 Oct 10, 2023
@poor-circle
Copy link
Collaborator

首先,struct_pack是支持非侵入式支持第三方库的。但前提条件是该类型可以作为合法的struct_pack类型。
struct_pack支持的类型请见:https://alibaba.github.io/yalantinglibs/zh/struct_pack/struct_pack_type_system.html

例如,对于boost::container::flat_map,以下代码是合法的:

boost::container::flat_map m;
auto buffer = struct_pack::serialize(m);
auto result = struct_pack::deserialize<boost::container::flat_map>(buffer);

@poor-circle
Copy link
Collaborator

其次,对于用户给定的结构体,如果只想序列化其中一部分字段,也可以做到非侵入式支持:

struct hi {
  int a;
  double b;
};
STRUCT_PACK_REFL(hi,a);
hi h;
struct_pack::serialize(h); // only save a;

@poor-circle
Copy link
Collaborator

NTL::ZZ 应该是一个大整数类型,不属于struct_pack的类型系统。因此,目前确实无法做到非侵入式支持。

我的想法是:对于struct_pack无法支持的类型,可以在类型系统中统一视作未知类型来处理。用户同样只需要自定义两个函数即可。
一个简单的设计如下:

namespace NTL{
   template<struct_pack::writer_t writer_t>
   void serialize_to(writer_t writer, const ZZ&zz);
   template<struct_pack::reader_t reader_t>
   void deserialize_to(ZZ&zz, reader_t reader);
}

具体设计有待进一步讨论。

@uchenily
Copy link
Author

感谢回答, 不过我对 NTL::ZZ 这个类不属于struct_pack支持的类型存在一些疑惑, (对非平凡结构体的概念有疑惑)

https://github.com/libntl/ntl/blob/main/include/NTL/ZZ.h

想了解下, 这里定义的ZZ类是通过什么判断属于非平凡结构体的?

@uchenily uchenily changed the title struct_pack非侵入式支持第三方库 struct_pack 自定义序列化反序列化函数支持类型系统以外的类型 Oct 10, 2023
@poor-circle
Copy link
Collaborator

感谢回答, 不过我对 NTL::ZZ 这个类不属于struct_pack支持的类型存在一些疑惑, (对非平凡结构体的概念有疑惑)
https://github.com/libntl/ntl/blob/main/include/NTL/ZZ.h
想了解下, 这里定义的ZZ类是通过什么判断属于非平凡结构体的?

大整数类其成员一定有指针,因此显然是非平凡的结构体。

struct_pack不支持NTL::ZZ的原因倒不是因为他是非平凡结构体,而是因为结构体的成员含有裸指针类型。struct_pack不支持序列化裸指针,因为无法确定指针类型的语义到底是指向对象还是数组。

@poor-circle poor-circle changed the title struct_pack 自定义序列化反序列化函数支持类型系统以外的类型 struct_pack support user-defined type Oct 13, 2023
@poor-circle poor-circle changed the title struct_pack support user-defined type struct_pack support user-defined type which is not in struct_pack type system Oct 13, 2023
@poor-circle
Copy link
Collaborator

poor-circle commented Oct 13, 2023

详细设计如下:对于每一个自定义类型,用户需要在相同namespace下定义三个函数:

  1. get_needed_size, 计算序列化数据所需的长度
  2. serialize_to, 用户通过自定义的序列化算法存储数据,写入的长度必须和get_needed_size相同。
  3. deserialize_to, 用户通过自定义的反序列化算法解析数据
#include <ylt/struct_pack.hpp>

namespace NTL{
  std::size_t get_needed_size(NTL::ZZ& z) {
      return sizeof(uint64_t)+NTL::BytesFromZZ(z);
  };
   template<struct_pack::writer_t writer_t>
   void serialize_to(writer_t writer, const ZZ&z) {
       std::vector<unsigned char> buf(NTL::NumBytes(z));
       NTL::BytesFromZZ(buf.data(), z, static_cast<long>(buf.size()));
       uint64_t sz=buf.size();
       struct_pack::write(writer,sz);
       struct_pack::write(writer,buf.data(),buf.size());
   }
   template<struct_pack::reader_t reader_t>
   void deserialize_to(reader_t reader,ZZ&z) {
       uint64_t sz;
       struct_pack::read(reader,sz);
       std::string_view buf;
       if constexpr( requires{reader.read_view(std::size_t{});}) {
         buf = reader.read_view(sz);
       }
       else {
         std::vector<unsigned char> tmp;
         buf.resize(sz);
         struct_pack::read(reader,tmp.data(),tmp.size());
         buf={tmp.data(),tmp.size()}
       }
       NTL::ZZFromBytes(z, buf.data(), static_cast<long>(buf.size()));
   }
}

@uchenily
Copy link
Author

必须定义 get_needed_size() 的原因是?

@poor-circle
Copy link
Collaborator

必须定义 get_needed_size() 的原因是?

算法限制,struct_pack的二进制格式+流式读写,导致必须先在序列化之前获取所需的长度。

@poor-circle
Copy link
Collaborator

emmm...这个要求似乎可以移除,不过需要对代码结构做一些调整,理论上可以将get_needed_size()作为一个可选项。

@poor-circle
Copy link
Collaborator

poor-circle commented Dec 5, 2023

任意类型的自定义序列化已支持,见 #514文档

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants