在取消引用的基类指针上增强序列化

Boost serialization over dereferenced base class pointer

本文关键字:增强 序列化 指针 基类 取消 引用      更新时间:2023-10-16

我对boost序列化有点问题。有许多示例显示了如何通过基类指针简单地使用BOOST_class_EXPORT和BOOST_LASS_EXPORT_IMPLEMENT来序列化派生类指针。这运行良好,没有任何问题。

但是,我不想序列化指针,因为另一端的反序列化应该在指针上再次进行,然后boost创建序列化对象的新实例。

我可以序列化一个取消引用的指针,然后在现有对象实例上再次反序列化,而不会出现问题,也不会创建新的实例。但是,当取消引用的指针在基类上时,在指针上序列化时,派生类不会按预期进行序列化。

工作示例:

Class A;
Class B : public A;
A* baseClass = new B();
ar << baseClass // works perfectly

不起作用的示例:

Class A;
Class B : public A;
A* baseClass = new B();
ar << *baseClass; // only A is serialized

我可以通过在派生类上进行简单的序列化来实现它,比如:

B* derivedClass = new B();
ar << *derivedClass; // works fine

但是我在结构中的所有引用都是基类类型的。此外,我无法序列化指针,因为在反序列化时,我不需要实例化新的对象,只需要在现有实例上"覆盖"内容。

我曾尝试序列化指针,并尝试在现有实例上进行反序列化,但这无法正常工作。当我说对现有实例进行反序列化时,我的意思是:

A* baseClass = new B();
// baseClass is used in the program and in a given moment, its contents must be overwrite, so:
ar >> *baseClass;

正如我所说,在反序列化时,我不需要基类的新实例。那么,有什么办法让它发挥作用吗?

我和你遇到了同样的问题所以我查看了boost的文档,它提供了一种解决问题的方法,我可以定义一个类D来管理派生对象,并使用ar.register_type来区分a b c类,就像这样:

 class base {
    ...
};
class derived_one : public base {
    ...
};
class derived_two : public base {
    ...
};
main(){
    ...
    base *b;
    ...
    ar & b; 
}

保存b时,应该保存什么类型的对象?加载b时,应该创建什么类型的对象?它应该是derived_one、derived_two类的对象,还是可能是base类的对象?

事实证明,序列化的对象的类型取决于基类(在本例中为基类)是否具有多转性。若基不是多态的,也就是说它并没有虚拟函数,那个么基类型的对象将被序列化。任何派生类中的信息都将丢失。如果这是想要的(通常不是),那么就不需要其他努力了。

如果基类是多态的,那么派生类型最多的对象(在本例中为derived_one或derived_two)将被序列化。要序列化哪种类型的对象的问题(几乎)由库自动处理。

当该类的对象第一次被序列化时,系统会将每个类"注册"到存档中,并为其分配一个序列号。下次该类的某个对象在同一存档中被序列化时时,该编号会写入存档中。因此,每个类在归档中都是唯一标识的。当回读归档文件时,每个新的序列号都会与正在读取的类重新关联。请注意,这意味着在保存和加载过程中都必须进行"注册",以便在加载时构建的类整数表与在保存时构建的类别整数表相同。事实上,整个序列化系统的关键是事物总是以相同的顺序保存和加载。这包括"注册"。

main(){
    derived_one d1;
    derived_two d2:
    ...
    ar & d1;
    ar & d2;
    // A side effect of serialization of objects d1 and d2 is that
    // the classes derived_one and derived_two become known to the archive.
    // So subsequent serialization of those classes by base pointer works
    // without any special considerations.
    base *b;
    ...
    ar & b; 
}

当b被读取时,它前面有一个唯一的(档案)类标识符,该标识符以前与类derived_one或derived_two有关。

如果派生类没有如上所述自动"注册",那么在调用序列化时将引发unregistered_class异常。

这可以通过显式注册派生类来解决。所有档案都是从实现以下模板的基类派生的:

template<class T>
register_type();

因此,我们的问题也可以通过以下方式来解决:

main(){
    ...
    ar.template register_type<derived_one>();
    ar.template register_type<derived_two>();
    base *b;
    ...
    ar & b; 
}

请注意,如果序列化函数在保存和加载之间拆分,则这两个函数都必须包含注册。这是保持保存和相应加载同步所必需的。

你也可以使用:

#include <boost/serialization/export.hpp>
...
BOOST_CLASS_EXPORT_GUID(derived_one, "derived_one")
BOOST_CLASS_EXPORT_GUID(derived_two, "derived_two")
main(){
    ...
    base *b;
    ...
    ar & b; 

}宏BOOST_CLASS_EXPORT_GUID将字符串文字与类相关联。在上面的示例中,我们使用了类名的字符串呈现。如果这样一个"导出"类的对象通过指针序列化,并且以其他方式未注册,则"导出"字符串将包含在存档中。当以后读取存档时,字符串文字用于查找应该由序列化库创建的类。这允许每个类与其字符串标识符一起位于一个单独的头文件中。不需要维护可能被序列化的派生类的单独"预注册"。这种注册方法被称为"密钥导出"。

也许对你有帮助!!有关详细信息,您可以看到:http://www.boost.org/doc/libs/1_54_0/libs/serialization/doc/index.html

我想我理解这个问题。当你做

ar >> *DerivedClass;

您正在向operator<<传递引用。现在,通过引用基类访问的对象没有正确序列化,正如我在Boost用户邮件列表中从Robert Ramey对这个问题的回答中收集到的那样。尽管答案已经有好几年的历史了,但我认为它仍然成立,因为,如果你仔细想想,一个人写的serialize方法不是虚拟的(它们是模板,所以不可能是虚拟的)。

因此,库必须做一些特殊的事情来处理指针,但它不是用引用来做的。我发现的一个丑陋的解决方案是添加一对(虚拟)序列化函数,如下所示:

virtual myser(iarchive &ia) {ia >> *this;}
virtual myser(oarchive &oa) {oa << *this;}

其中iarchiveoarchive应替换为所需的存档。这真的很糟糕,因为除了必须编写两个额外的函数外,还必须明确地为所有需要的归档类型重载它们。不幸的是,我不知道有什么更好的解决方案。