在 Boost (C++) 中没有类跟踪的派生类序列化

Posted

技术标签:

【中文标题】在 Boost (C++) 中没有类跟踪的派生类序列化【英文标题】:Derived class serialization without class tracking in Boost (C++) 【发布时间】:2013-03-14 21:24:42 【问题描述】:

在通过基类指针序列化派生类时,boost 序列化存在一些问题。我需要一个系统来序列化系统中接收到的一些对象,所以我需要随着时间的推移进行序列化。这不是一个真正的问题,因为我可以打开 boost::archive::binary_oarchive 并在需要时序列化对象。很快我注意到 boost 是通过内存地址执行对象跟踪,所以第一个问题是共享相同内存地址的不同对象及时保存为同一个对象。这可以通过在所需的派生类中使用以下宏来解决:

BOOST_CLASS_TRACKING(className, boost::serialization::track_never)

这很好用,但同样,当基类不是抽象的时,基类没有正确序列化。在以下示例中,基类序列化方法仅对第一个对象调用一次。在下文中,boost 假设该对象之前已经序列化,尽管该对象的类型不同。

#include <iostream>
#include <fstream>
#include <boost/serialization/export.hpp>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/list.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/archive/archive_exception.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>

using namespace std;

class AClass
public:
    AClass()
    virtual ~AClass()
private:
    double a;
    double b;
    //virtual void virtualMethod() = 0;
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    
        ar & a;
        ar & b;
        cout << "A" << endl;
    
;
//BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)
//BOOST_CLASS_TRACKING(AClass, boost::serialization::track_never)

class BClass : public AClass
public:
    BClass()
    virtual ~BClass()
private:
    double c;
    double d;
    virtual void virtualMethod();
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    
        ar & boost::serialization::base_object<AClass>(*this);
        ar & c;
        ar & d;
        cout << "B" << endl;
    
;
// define export to be able to serialize through base class pointer
BOOST_CLASS_EXPORT(BClass)
BOOST_CLASS_TRACKING(BClass, boost::serialization::track_never)


class CClass : public AClass
public:
    CClass()
    virtual ~CClass()
private:
    double c;
    double d;
    virtual void virtualMethod();
private:
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    
        ar & boost::serialization::base_object<AClass>(*this);
        ar & c;
        ar & d;
        cout << "C" << endl;
    
;
// define export to be able to serialize through base class pointer
BOOST_CLASS_EXPORT(CClass)
BOOST_CLASS_TRACKING(CClass, boost::serialization::track_never)

int main() 
    cout << "Serializing...." << endl;
    
        ofstream ofs("serialization.dat");
        boost::archive::binary_oarchive oa(ofs);
        for(int i=0;i<5;i++)
        
            AClass* baseClassPointer = new BClass();
            // serialize object through base pointer
            oa << baseClassPointer;
            // free the pointer so next allocation can reuse memory address
            delete baseClassPointer;
        

        for(int i=0;i<5;i++)
        
            AClass* baseClassPointer = new CClass();
            // serialize object through base pointer
            oa << baseClassPointer;
            // free the pointer so next allocation can reuse memory address
            delete baseClassPointer;
        
    
    getchar();
    cout << "Deserializing..." << endl;
    
        ifstream ifs("serialization.dat");
        boost::archive::binary_iarchive ia(ifs);
        try
            while(true)
                AClass* a;
                ia >> a;
                delete a;
            
        catch(boost::archive::archive_exception const& e)
        

        
    
    return 0;

执行这段代码时,结果如下:

Serializing....
A
B
B
B
B
B
C
C
C
C
C

Deserializing...
A
B
B
B
B
B
C
C
C
C
C

因此基类只被序列化一次,尽管派生类有明确的 track_never 标志。有两种不同的解决方法可以解决此问题。第一个是用纯虚方法抽象基类并调用宏BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass),第二个是将track_never标志也放在基类中(代码中注释)。

这些解决方案都不符合我的要求,因为我想在未来对系统状态进行准时序列化,这需要跟踪给定 DClass 扩展 A(不是 B 或 C)的功能,而且 AClass 不应该抽象一点。

有什么提示吗?有什么方法可以显式调用基类序列化方法,避免基类中的跟踪功能(派生类中已经禁用)?

【问题讨论】:

可以让 boost 假设 A 是虚拟的,即使它不是(我的意思是,只需取消注释行 BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass) 并尝试编译)? 【参考方案1】:

最终的问题似乎是boost::serialization 存档表示单个时间点的状态,并且您希望存档包含已更改的状态,即已重用的指针。我不认为有一个简单的boost::serialization 标志会引发你想要的行为。

但是,我认为还有其他解决方法可能就足够了。您可以将一个类的序列化封装到它自己的归档中,然后将封装归档。也就是说,您可以像这样实现B 的序列化(注意您必须将serialize() 拆分为save()load()):

// #include <boost/serialization/split_member.hpp>
// #include <boost/serialization/string.hpp>
// Replace serialize() member function with this.

template<class Archive>
void save(Archive& ar, const unsigned int version) const 
  // Serialize instance to a string (or other container).
  // std::stringstream used here for simplicity.  You can avoid
  // some buffer copying with alternative stream classes that
  // directly access an external container or iterator range.
  std::ostringstream os;
  boost::archive::binary_oarchive oa(os);
  oa << boost::serialization::base_object<AClass>(*this);
  oa << c;
  oa << d;

  // Archive string to top level.
  const std::string s = os.str();
  ar & s;
  cout << "B" << endl;


template<class Archive>
void load(Archive& ar, const unsigned int version) 
  // Unarchive string from top level.
  std::string s;
  ar & s;

  // Deserialize instance from string.
  std::istringstream is(s);
  boost::archive::binary_iarchive ia(is);
  ia >> boost::serialization::base_object<AClass>(*this);
  ia >> c;
  ia >> d;
  cout << "B" << endl;


BOOST_SERIALIZATION_SPLIT_MEMBER()

因为B 的每个实例都被序列化到自己的存档中,所以A 实际上不会被跟踪,因为B 的每个存档只有一个引用。这会产生:

Serializing....
A
B
A
B
A
B
A
B
A
B
A
C
C
C
C
C

Deserializing...
A
B
A
B
A
B
A
B
A
B
A
C
C
C
C
C

对该技术的一个潜在反对意见是封装的存储开销。原始测试程序的结果是 319 字节,而修改后的测试程序产生 664 字节。但是,如果将 gzip 应用于两个输出文件,则原始文件的大小为 113 字节,修改后的文件大小为 116 字节。如果空间是一个问题,那么我建议在外部序列化中添加压缩,这可以通过boost::iostreams 轻松完成。

另一个可能的解决方法是将实例的生命周期延长到存档的生命周期,这样指针就不会被重用。您可以通过将 shared_ptr 实例的容器与您的存档相关联,或从内存池中分配实例来做到这一点。

【讨论】:

添加了关于封装存档的存储开销的注释。 谢谢...但是这个解决方案使序列化过程复杂化并且增加了它的大小。我也不能延长指针的寿命,因为系统打算无限期地工作。无论如何感谢您的帮助!【参考方案2】:

在仔细研究 boost::serialization 之后,我也确信没有直接的解决方案可以满足您的要求。 正如您已经提到的,序列化的跟踪行为是使用 BOOST_CLASS_TRACKING 在一个类一个类的基础上声明的。 这个 const 全局信息比从类 oserializer 跟踪的虚拟方法中解释。

   virtual bool tracking(const unsigned int /* flags */)

因为这是一个模板类,您可以为您的类显式实例化此方法。

namespace boost 
namespace archive 
namespace detail 

template<>
    virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const 
        return do_your_own_tracking_decision();
    


现在您可以尝试使用诸如全局变量之类的东西并不时更改跟踪行为。 (例如,取决于将哪个派生类写入存档。) 这似乎适用于“序列化”,但“反序列化”会引发异常。 这样做的原因是,每个类的“跟踪”状态只是写入存档的状态。因此,如果读取 BClass 或 CClass,反序列化总是期望 AClass 的数据(如果 AClass 的第一次写入尝试禁用跟踪)。

一种可能的解决方案是在 tracking() 方法中使用 flags 参数。 此参数表示创建存档的标志,默认为“0”。

binary_oarchive(std::ostream & os, unsigned int flags = 0) 

归档标志在 basic_archive.hpp 中声明

enum archive_flags 
    no_header = 1,  // suppress archive header info
    no_codecvt = 2,  // suppress alteration of codecvt facet
    no_xml_tag_checking = 4,   // suppress checking of xml tags
    no_tracking = 8,           // suppress ALL tracking
    flags_last = 8
;

目前似乎不支持 no_tracking,但您现在可以将此行为添加到跟踪中。

template<>
    virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const 
        return !(f & no_tracking);
     

现在您可以针对不同的档案决定是否应该跟踪 AClass。

 boost::archive::binary_oarchive oa_nt(ofs, boost::archive::archive_flags::no_tracking);

这就是你的例子中的变化。

int main() 
    cout << "Serializing...." << endl;
    
        ofstream ofs("serialization1.dat");
        boost::archive::binary_oarchive oa_nt(ofs, boost::archive::archive_flags::no_tracking);
        //boost::archive::binary_oarchive oa(ofs);
        for(int i=0;i<5;i++)
        
            AClass* baseClassPointer = new BClass();
            // serialize object through base pointer
            oa_nt << baseClassPointer;
            // free the pointer so next allocation can reuse memory address
            delete baseClassPointer;
        

        ofstream ofs2("serialization2.dat");
        boost::archive::binary_oarchive oa(ofs2);
        //boost::archive::binary_oarchive oa(ofs);

        for(int i=0;i<5;i++)
        
            AClass* baseClassPointer = new CClass();
            // serialize object through base pointer
            oa << baseClassPointer;
            // free the pointer so next allocation can reuse memory address
            delete baseClassPointer;
        
    
    getchar();
    cout << "Deserializing..." << endl;
    
        ifstream ifs("serialization1.dat");
        boost::archive::binary_iarchive ia(ifs);
        try
            while(true)
                AClass* a;
                ia >> a;
                delete a;
            
        catch(boost::archive::archive_exception const& e)
        

        

        ifstream ifs2("serialization2.dat");
        boost::archive::binary_iarchive ia2(ifs2);
        try
            while(true)
                AClass* a;
                ia2 >> a;
                delete a;
            
        catch(boost::archive::archive_exception const& e)
        

        

    
    return 0;



namespace boost 
namespace archive 
namespace detail 

template<>
    virtual bool oserializer<class binary_oarchive, class AClass >::tracking(const unsigned int f /* flags */) const 
        return !(f & no_tracking);
    


这可能仍然不是您想要的。还有更多方法可以通过自己的实现进行调整。或者您必须派生自己的存档类。

【讨论】:

似乎是个好办法!我认为它可以满足我的要求。让我明天更详细地检查和测试您的回复。我会告诉你的。谢谢! 最后我花了一些时间来测试你的方法,但它似乎不起作用。它在虚拟跟踪方法中出现错误,因为它不在 oserializer 类范围内。 “虚拟外部类声明”。而且我无法修改 boost 库:( 终于让它工作了!我可以打开带有标志的文件并选择何时进行跟踪!谢谢!我编辑了您的代码以便能够编译。这种做法似乎符合我的要求!

以上是关于在 Boost (C++) 中没有类跟踪的派生类序列化的主要内容,如果未能解决你的问题,请参考以下文章

boost::serialization 用基类指针转存派生类(错误多多,一波三折)

使用 Boost.Python 设置包装类的元类

使用Boost将派生类部分反序列化为基类时输入流错误

C ++在派生类中专门化继承模板

Boost 将派生类反序列化为基类指针

如何使用 Boost.serialize 序列化派生模板类?