接近没有公共基类的序列化

Approaching serialization without common base class

本文关键字:基类 序列化 接近      更新时间:2023-10-16

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

我正在使用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几何没有有一个公共基类。

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

我想也许有一个序列化器类,返回一个提升。然后可以使用存储在(反)序列化器中的类型对几何形状进行强制转换?但是,串行化程序需要为每个几何类型提供保存方法吗?例如:Save(myPolygon), Save(myPoint)

思想/经验吗?

如果您不希望重新实现转轮,Boost的序列化支持非侵入式序列化。您甚至可以在某处找到支持它们的几何类型的库。不幸的是,由于XML问题,接口有些复杂。

要将对象序列化为字节,您最终需要为必须支持的每种类型(原语,对象等)提供2个函数。它们是"Load()"answers"Store()"。

理想情况下,您可以为字节使用固定接口——iostream、char*、某些缓冲区对象等。为了便于阅读,我们称它为"ByteBuffer",因为逻辑上这就是它的角色。

我们现在有了类似于Serializable概念的模板函数:

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);

为了检查错误,可以在基类中提供一个保护标志"valid",派生构造函数可以设置该标志。注意,你的对象必须支持工厂风格的构造,这样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()",它会做你所期望的。此外,"Load"可以被设置为构建对象的唯一方式,允许您为只读文件等清理api。

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

作为旁注,不要使用boost的api。在良好的设计中,可序列化对象应该一对一地映射到磁盘上的基本类型的打包结构——这是生成的文件真正能够被其他程序或其他机器使用的唯一方法。Boost提供了一个糟糕的API,它让你做了很多以后会后悔的事情。