15

通过基类指针序列化派生类时,我在提升序列化方面遇到了一些问题。我需要一个系统来序列化系统中接收到的一些对象,所以我需要随着时间的推移进行序列化。这不是一个真正的问题,因为我可以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 不应该是抽象的。

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

4

2 回答 2

2

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

但是,我认为还有其他解决方法可能就足够了。您可以将一个类的序列化封装到它自己的归档中,然后将封装归档。也就是说,您可以B像这样实现序列化(请注意,您必须拆分serialize()save()and 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实例容器与存档相关联,或从内存池中分配实例来实现此目的。

于 2013-04-04T17:06:32.387 回答
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);
    }

}}}

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

于 2013-04-15T14:45:22.410 回答