【问题标题】:How do you 'de-serialize' a derived class from serialized data?如何从序列化数据中“反序列化”派生类?
【发布时间】:2011-03-17 04:14:53
【问题描述】:

如何从序列化数据中“反序列化”派生类?或者我应该说,有没有更好的方法将数据“反序列化”到派生类中?

例如,假设您有一个纯虚拟基类 (B),它被其他三个类 X、Y 和 Z 继承。此外,我们有一个方法 serialize(),它将转换 X:B、Y :B 和 Z:B 转化为序列化数据。

通过这种方式,它可以通过套接字、命名管道等传输到远程进程。

我的问题是,我们如何从序列化的数据中创建一个合适的对象?

我能想到的唯一解决方案是在序列化数据中包含一个标识符,该标识符指示最终派生对象类型。接收者首先从序列化数据中解析派生类型字段,然后使用 switch 语句(或某种类似的逻辑)来调用适当的构造函数。

例如:

B deserialize( serial_data )
{
    parse the derived type from the serial_data

    switch (derived type)
        case X
            return X(serial_data)
        case Y
            return Y(serial_data)
        case Z
            return Z(serial_data)
}

所以在了解了派生对象类型之后,我们调用了适当的派生类型构造函数。

但是,这感觉很尴尬和麻烦。我希望有一种更雄辩的方式来做到这一点。有吗?

【问题讨论】:

  • 紧凑序列化(按位)可能会咬紧牙关,特别是如果您打算将内容保存到文件中。随着岁月的流逝和课程的变化,当您再次尝试加载它们时,您几乎被搞砸了。出于这个原因,使文件自我记录和版本控制是一个好主意。只有当客户端和服务器始终就所使用的协议达成一致时,才可以发送直接字节。如果没有,那么我会求助于XML/JSON。但是,我也会研究可以使这更容易的东西,例如 SOAP 等。
  • @Hamish +1,请回答!

标签: c++ serialization


【解决方案1】:
在记忆中: -------- 类型 1 { 字符型 a; 类型 b; }; 序列化(新类型 1()); 序列化(忽略 { 和 ,): -------------------------- type1id,len{chartypeid,adata,inttypeid,bdata}

我猜,在理想的序列化协议中,每个非原始类型都需要以 typeid,len 作为前缀。即使你序列化一个不是从任何东西派生的单一类型,你也会添加一个类型 id,因为另一端必须知道它得到什么类型(不管继承结构如何)。所以你必须在序列化中提到派生类id,因为从逻辑上它们是不同的类型。如果我错了,请纠正我。

【讨论】:

  • 这是我前进的方向,但我认为不需要长度。当然,这取决于 typeid 的定义方式。我将其设想为识别类的类型,然后也可以推断出长度。但我怀疑我的想法也是基于我使用 POSIX 消息队列进行传输的事实,因此传输层知道长度。
【解决方案2】:

事实上,这是一个比序列化更普遍的问题,称为Virtual Constructor

传统的方法是Factory,它基于一个ID 返回正确的派生类型。有两种解决方案:

  • switch 方法如您所见,但您需要在堆上分配
  • prototype 方法

原型方法如下:

// Cloneability
class Base
{
public:
  virtual Base* clone() const = 0;
};

class Derived: public Base
{
public:
  virtual Derived* clone() const { return new Derived(*this); }
};

// Factory
class Factory
{
public:
  Base* get(std::string const& id) const;
  void set(std::string const& id, Base* exemplar);

private:
  typedef std::map < std::string, Base* > exemplars_type;
  exemplars_type mExemplars;
};

Factory 设为单例有点传统,但这完全是另一回事。

为了适当的反序列化,如果你有一个虚拟方法deserialize 来调用对象会更容易。

编辑:工厂如何运作?

在 C++ 中,您无法创建您不知道的类型。因此,上面的想法是通过clone 方法将构建Derived 对象的任务交给Derived 类。

接下来是Factory。我们将使用map,它将一个“标签”(例如"Derived")关联到一个对象的实例(比如这里的Derived)。

Factory factory;
Derived derived;
factory.set("Derived", &derived);

现在,当我们想要创建一个我们在编译时不知道类型的对象时(因为类型是动态决定的),我们将一个标签传递给工厂并要求一个对象作为回报。

std::unique_ptr<Base> base = factory.get("Derived");

在封面下,Factory 将找到与"Derived" 标签关联的Base*,并调用对象的clone 方法。这实际上将(在这里)创建一个运行时类型的对象Derived

我们可以使用typeid 运算符来验证这一点:

assert( typeid(base) == typeid(Derived) );

【讨论】:

  • 原谅我的无知。但我不明白工厂如何解决问题。事实上,我是在上面的工厂课上从卡车上摔下来的。
  • 没问题,我们都是来学习的,我加了工厂如何解决虚拟建造问题的解释。不要对map 结构感到困惑,它只是一个可以在运行时填充的花哨开关。
  • 感谢您提供更多信息。所以如果我理解正确的话。我们仍然需要某种标签来告诉我们它是什么类型的对象。工厂使用这个标签来查找返回新克隆的“注册”函数。如果我错了,请纠正我,但这看起来像一个“动态”开关语句。工厂没有像我上面那样使用硬编码开关,而是为您提供了一种“灵活”的方式来提供相同的功能?或者我错过了什么。顺便说一句,谢谢!我已经针对我遇到的另一个问题调整了这个解决方案!
  • @John Rocha:你完全正确!对于这个“动态” switch 语句,您需要一个标签。它与您的解决方案没有太大区别,唯一的优点是动态位,因为您不必每次向集合中添加新类型时都修改同一个文件(切换)。
猜你喜欢
  • 2012-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多