理解C ++中多态对象的序列化

时间:2017-08-06 09:06:30

标签: c++ serialization polymorphism

编辑:我意识到下面的代码是一个很好的例子,说明你在C ++中不能做任何不是POD的事情。

似乎没有一种方法可以避免将typeid放入类中,并在接收器端进行某种切换或表查找(两者都必须小心维护)以重建对象。

我已经写了一些玩具代码来序列化对象和两个独立的主管来写/从文件中读取它们。

COMMON.H:

#include <iostream>
using namespace std;

template <typename T>
size_t serialize(std::ostream & o, const T & t) {
  const char * bytes = reinterpret_cast<const char*>(&t);
  for (size_t i = 0; i < t.size(); ++i) {
    o << bytes[i];
  }
  return t.size();
}

size_t deserialize(std::istream & i, char * buffer) {
  size_t len = 0;
  char c;
  while (i.get(c)) {
    buffer[len] = c;
    ++len;
  }
  return len;
}

// toy classes
struct A {
  int a[4];
  virtual ~A() {}
  virtual void print(){cout << "A\n";}
  virtual size_t size() const {return sizeof(*this);}
};
struct B: A {
  int b[16];
  virtual ~B() {}
  virtual void print(){cout << "B\n";}
  virtual size_t size() const {return sizeof(*this);}
};

out.cpp:

#include <fstream>
#include "common.h"

int main() {
  B b;
  A& a = *static_cast<A*>(&b);
  ofstream ofile("serial.bin");
  cout << "size = " << serialize(ofile, a) << endl;
  ofile.close();
  return 0;
}

in.cpp:

#include <fstream>
#include "common.h"

int main() {
  char buffer[1024];
  ifstream ifile("serial.bin");
  cout << "size = " << deserialize(ifile, buffer) << endl;
  ifile.close();
  A& a = *reinterpret_cast<A*>(buffer);
  a.print();
  return 0;
}

如果我的课程没有虚拟功能,这似乎工作正常,但是 in.cpp崩溃了。

我的理解是out.cpp创建的vptr不适合in.cpp使用。

是否有可以完成的事情,可能避免手动创建和维护vtable?

1 个答案:

答案 0 :(得分:3)

如果你绝对不能使用任何库(因为仍然可能有一些选项,即使对于嵌入式平台),序列化多态类的一个选项可能是提供虚拟序列化/反序列化方法。

在这种情况下例如:

struct A {
  int a[4];
  virtual ~A() {}
  virtual void print(){cout << "A\n";}
  virtual size_t size() const {return sizeof(*this);}
  virtual void serialize(std::ostream & o) const
  {
      for (int i = 0; i < 4; ++i) o << a[i];
  }
  virtual void deserialize(std::istream & i)
  {
      for (int i = 0; i < 4; ++i) i >> a[i];
  }
};
struct B: A {
  int b[16];
  virtual ~B() {}
  virtual void print(){cout << "B\n";}
  virtual size_t size() const {return sizeof(*this);}
  virtual void serialize(std::ostream & o) const
  {
      A::serialize(o);
      for (int i = 0; i < 16; ++i) o << b[i];
  }
  virtual void deserialize(std::istream & i)
  {
      A::deserialize(i);
      for (int i = 0; i < 16; ++i) i >> b[i];
  }
};

// prg 1
B b;
b.serialize(ofile);

// prg 2
B.b;
b.deserialize(ifile);

基本上,您将逐个将特定成员写入文件。

但是,这只是为了简单的说明您实际上知道您希望在文件中使用哪个类。如果可以有多个类,则还需要编写一些类标识(例如一些struct serialization id)来知道要读取的类。此外,如果类可能会更改,您可能需要对类进行某种版本化。

如上所述,指针也很棘手,特别是因为它们可以为NULL - 您可以先写一个bool(byte)来确定指针是否为NULL,然后是内容(如果有的话)。类似的方法,您可以序列化/反序列化,例如std :: string或std :: vector:首先写长度,然后写项目。阅读时,您将读取长度,保留或调整字符串/向量的大小,然后阅读项目。

另一个问题可能是文件是否传输到不同的机器,可能有不同的字节顺序(endian)。正如您所看到的,如果仍有一些库可用,最好使用它而不是从头开始编写所有内容。

要添加多态反序列化(我可以看到你只在读者端使用A),你可以拥有例如:

struct A {
  ...
  virtual int get_serialization_id() const = 0;
};
struct B: A {
  ...
  static const int SERIALIZATION_ID = 1; // needs to be different in every polymorphic class
  virtual int get_serialization_id() const
  { return SERIALIZATION_ID; }
};

void serialize(std::ostream & o, const A & a)
{
  o << a.get_serialization_id();
  o << a.serialize();
}

std::unique_ptr<A> deserialize(std::istream & i)
{
  std::unique_ptr<A> result;
  int id;
  i >> id;
  switch (id)
  {
  case B::SERIALIZATION_ID:
    result = std::make_unique<B>();
    break:
  case C::SERIALIZATION_ID:
    result = std::make_unique<C>();
    break:
  ...
  default:
    // leave NULL or throw exception
    return result;
  }
  result->deserialize(i);
  return result;
}

为避免切换,您可以更加花哨并提供某种工厂注册(在地图中注册序列化ID以及类工厂,然后使用注册表查找工厂并创建类)。你可以非常喜欢反序列化:)。

请注意,有些情况确实难以解决(例如,重新创建具有指向来自多个其他实例的同一实例的共享指针的实例结构等)。