15

基本クラスポインタを介して派生クラスをシリアル化するときに、ブーストシリアル化に問題があります。システムで受信されているオブジェクトをシリアル化するシステムが必要なので、時間をかけてシリアル化する必要があります。boost::archive::binary_oarchive必要に応じてオブジェクトを開いてシリアル化できるため、これは実際には問題ではありません。Boostがメモリアドレスによるオブジェクトトラッキングを実行していることにすぐに気付きました。最初の問題は、同じメモリアドレスを共有する時間内の異なるオブジェクトが同じオブジェクトとして保存されることでした。これは、必要な派生クラスで次のマクロを使用することで修正できます。

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

これは正常に機能しますが、基本クラスが抽象でない場合、基本クラスは適切にシリアル化されません。次の例では、基本クラスのシリアル化メソッドは、最初のオブジェクトで1回だけ呼び出されます。以下では、オブジェクトのタイプは異なりますが、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フラグがありますが、基本クラスは1回だけシリアル化されます。この動作を修正するには、2つの異なる回避策があります。1つ目は、純粋仮想メソッドを使用して基本クラスを抽象化し、マクロを呼び出すことBOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)です。2つ目は、基本クラスにもtrack_neverフラグを配置することです(コードでコメント化)。

これらのソリューションはいずれも私の要件を満たしていません。将来、システム状態の時間的なシリアル化を実行したいので、Aを拡張する特定のDClass(BまたはCではない)の追跡機能が必要になります。また、AClassは抽象的であってはなりません。

ヒントはありますか?基本クラス(派生クラスですでに無効になっている)の追跡機能を回避して、基本クラスのシリアル化メソッドを明示的に呼び出す方法はありますか?

4

2 に答える 2

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ごとに参照が1つしかないため、事実上追跡されません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

もう1つの考えられる回避策は、インスタンスの有効期間をアーカイブの有効期間まで延長して、ポインターが再利用されないようにすることです。これを行うには、インスタンスのコンテナを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の最初の書き込み試行が追跡を無効にした場合はリースされます)。

考えられる解決策の1つは、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 に答える