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

时间:2013-03-25 11:43:30

标签: c++ serialization boost boost-serialization

在通过基类指针序列化派生类时,我遇到了一些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也不应该是抽象的。

任何提示?有没有办法显式调用基类序列化方法,避免基类中的跟踪功能(已在派生类中禁用)?

2 个答案:

答案 0 :(得分:2)

最终问题似乎是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个实例的容器与存档相关联,或者通过从内存池中分配实例来完成此操作。

答案 1 :(得分: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);
    }

}}}

这可能不是你想要的。有许多方法可以用自己的实现进行调整。或者您必须派生自己的档案类。