2013-03-25 2 views
15

派生クラスをベースクラスポインタを使用してシリアライズするときに、ブーストのシリアライゼーションに問題があります。いくつかのオブジェクトがシステムで受信されるときにそれらを直列化するシステムが必要なので、私は時間の経過とともにシリアル化する必要があります。 boost::archive::binary_oarchiveを開き、必要に応じてオブジェクトをシリアル化できるので、これは実際問題ではありません。急速に私は、メモリアドレスによるオブジェクトトラッキングを実行していることに気付きました。最初の問題は、同じメモリアドレスを共有する異なるオブジェクトが同じオブジェクトとして保存されたことでした。これは、必要な派生クラスで次のマクロを使用して固定することができます。Boost(C++)でのクラストラッキングのない派生クラスのシリアライゼーション

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

これは正常に動作しますが、基本クラスが抽象的でない場合、再び、ベースクラスが適切にシリアライズされていません。次の例では、基本クラスの直列化メソッドは、最初のオブジェクトで1回だけ呼び出されます。以下では、オブジェクトは異なる型を持っていますが、このオブジェクトは前もってシリアル化されていると仮定しています。

#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; 
} 

コードのこの部分を実行する場合、結果は次の通りである:

​​

派生クラスが明示的track_neverフラグを有するができるように、基本クラスのみ、一度シリアル化されています。この動作を修正するには、2つの異なる回避策があります。最初の方法は、基本クラスを純粋な仮想メソッドで抽象化し、マクロBOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)を呼び出すことです。もう1つは、track_neverフラグを基底クラス(コード内にコメントされている)に入れます。

システム状態の将来の厳密なシリアル化を実行したいと思っています。これは、特定のDClass拡張A(BまたはCではなく)を追跡する必要があります。また、AClassは抽象的ではない。

ヒント明示的にベースクラスの直列化メソッドを呼び出す方法はありますか?これは、ベースクラスのトラッキング機能を避けることです(既に派生クラスでは無効になっています)。

+0

boostが仮想でないと仮定することもできます( 'BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass)'行のコメントを外してコンパイルしようとしています)? – Synxis

答えて

2

私はまた、あなたに簡単な解決策がないと確信しています。 すでに説明したように、シリアライゼーションのトラッキング動作は、クラスベースで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); 
    } 

}}} 

これはまだあなたが探しているものではありません。独自の実装に適合させることができる多くのメソッドがあります。または独自のアーカイブクラスを派生させる必要があります。

+0

良いアプローチと思われます!私はそれが私の要求を満たすことができると思う。私はあなたの応答を明日より詳細にチェックし、テストしましょう。私はあなたに伝えます。ありがとう! –

+0

@alvarolb私はちょっと不思議です。それはあなたのために働いたのですか? –

+0

最後に、あなたのアプローチをテストするのに時間がかかるが、うまくいかないようだ。 oserializerクラスのスコープ内にないため、仮想トラッキングメソッドでエラーが発生します。 "仮想外部クラス宣言"。そして、私はブーストライブラリを変更することはできません:( –

2

最終的には、boost::serializationアーカイブは1つの時点の状態を表し、アーカイブには変更された状態、つまり再利用されたポインタが含まれるようにしたいと考えています。私は単純なboost::serializationフラグがあなたが望む振る舞いを誘発するとは思わない。

しかし、他にも十分な回避策があると思います。あるクラスのシリアライゼーションを独自のアーカイブにカプセル化し、カプセル化をアーカイブすることができます。それはあなたが(あなたがsave()load()serialize()を分割する必要があることに注意してください)このようなBのためのシリアル化を実装することができ、次のとおりです。Bの各インスタンスは、独自のアーカイブに連載されているので

// #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() 

Aが効果的に追跡されていませんアーカイブごとに参照番号Bが1つしかないためです。これは、次のようになります。

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のインスタンスのコンテナをアーカイブに関連付けるか、またはメモリプールからインスタンスを割り当てることによって、これを行うことができます。

+0

アーカイブをカプセル化する際のストレージオーバーヘッドに関する注意を追加しました。 – rhashimoto

+0

ありがとう...しかし、このソリューションは、シリアル化プロセスを複雑にし、そのサイズも大きくします。私はシステムが無限に働くことを意図しているので、ポインターの寿命を延ばすこともできません。とにかく助けてくれてありがとう! –

関連する問題