【问题标题】:Approaching serialization without common base class在没有公共基类的情况下接近序列化
【发布时间】:2011-09-07 16:36:09
【问题描述】:

我正在为以下问题寻找一些设计建议:

我正在使用 boost 几何,我有几个与 boost 几何兼容的自定义几何类型(通过特征),但我使用的大多数类型都是 typedef。

class MyPoint
{
  // custom stuff
};
// declare traits for MyPoint for use wih boost geometry here

class MyTaggedPoint : public MyPoint
{
  // more custom stuff
};
// declare traits for MyTaggedPoint for use wih boost geometry here

// example typedefs
typedef boost::geometry::model::polygon<MyPoint>        Polygon;
typedef boost::geometry::model::polygon<MyTaggedPoint>  TaggedPolygon;

我的问题是当我想序列化/反序列化我的几何图形时。

假设所有几何图形都存储在数据库的二进制字段中。如果我有一个基本几何类,我可能只写 g->type()(4 个字节)并调用 g->save(some_outputstream) 并将所有这些写入二进制字段。然后在读取二进制字段时,我只需读取字节并转换为适当的几何类型。

但是 Boost 几何结构没有有一个共同的基类。

当有多种类型可以存储为二进制而你没有共享基类时,你们通常如何处理序列化?

我在想也许有一个 Serializer 类,它返回一个 boost.Any,然后可以使用将存储在(反)序列化器中的类型来转换几何?但是,序列化程序需要为每种几何类型提供一个保存方法吗?例如:保存(myPolygon),保存(myPoint)

有什么想法/经验吗?

【问题讨论】:

    标签: c++ serialization boost boost-geometry


    【解决方案1】:

    Boost's serialization 如果您不想重新实现轮子,则支持非侵入式序列化。您甚至可以在某处找到对其几何类型的库支持。不幸的是,由于 XML 问题,接口有些复杂。

    【讨论】:

    • 为 Boost.Serialization +1,特别是因为他已经在使用 Boost。
    【解决方案2】:

    要在字节之间序列化对象,您最终需要 2 个函数来支持您必须支持的每种类型(原语、对象等)。它们是“Load()”和“Store()”。

    理想情况下,您对字节使用固定接口 - iostream、char*、一些缓冲区对象等。 为了便于阅读,我们称它为“ByteBuffer”,因为从逻辑上讲,这就是它的作用。

    我们现在有了类似可序列化概念的模板函数:

    template<typename T>
    ByteBuffer Store(const T& object) { // BUT, What goes here...? }
    
    template<typename T>
    T Load(const ByteBuffer& bytes);
    

    好吧,这对除了原始类型之外的任何东西都不起作用——即使我们让这些“访问者”或者他们确实必须了解对象内部的每一个细节才能完成他们的工作。此外,“Load()”在逻辑上是一个构造函数(实际上是一个 FACTORY,因为它很容易失败)。我们必须将这些与实际对象相关联。

    为了使 Serializable 成为一个基类,我们需要使用“奇怪地重复出现的模板”模式。为此,我们要求所有派生类都具有以下形式的构造函数:

    T(const ByteBuffer& bytes);
    

    为了检查错误,我们可以在派生构造函数可以设置的基类中提供一个受保护的标志“有效”。请注意,您的对象必须支持工厂风格的构造,才能使 Load() 正常工作。

    现在我们可以做到这一点,将“Load”作为工厂提供:

    template<typename T>
    class Serializable // If you do reference-counting on files & such, you can add it here
    {
    protected:
        bool valid;
    
        // Require derived to mark as valid upon load
        Serializable() : valid(false) {}
        virtual ~Serializable() { valid = false; }
    
    public:
        static T Load(const ByteBuffer& bytes); // calls a "T(bytes)" constructor
    
        // Store API
        virtual ByteBuffer Store() = 0;  // Interface details are up to you.
    };
    

    现在,只需像这样从基类派生,您就可以获取所需的一切:

    class MyObject : public Serializable<MyObject>
    {
    protected:
       // .. some members ...
    
       MyObject(const ByteBuffer& bytes)
       {
          //... Actual load logic for this object type ...
          // On success only:
          valid = true;
       }
    
    public:
       virtual ByteBuffer Store() {
          //... store logic 
       }
    };
    

    很酷的是,您可以调用“MyObject::Load()”,它会完全按照您的预期进行。此外,“加载”可以成为构建对象的唯一方式,允许您为只读文件等清理 API。

    将此扩展到完整的文件 API 需要更多工作,即添加一个可以从更大的缓冲区(保存其他东西)读取的“Load()”和附加到现有缓冲区的“Store()”。

    附带说明,不要为此使用 boost 的 API。在一个好的设计中,可序列化对象应该一对一映射到磁盘上原始类型的打包结构——这是生成的文件真正可供其他程序或其他机器使用的唯一方式。 Boost 为您提供了一个糟糕的 API,它主要使您能够做您以后会后悔的事情。

    【讨论】:

      猜你喜欢
      • 2020-10-12
      • 1970-01-01
      • 2018-09-19
      • 2021-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-26
      相关资源
      最近更新 更多