2013-03-25 15 views
15

Ho alcuni problemi con la serializzazione boost durante la serializzazione della classe derivata tramite il puntatore della classe base. Ho bisogno di un sistema che serializzi alcuni oggetti mentre vengono ricevuti nel sistema, quindi ho bisogno di serializzare nel tempo. Questo non è davvero un problema dal momento che posso aprire un oggetto boost::archive::binary_oarchive e serializzare quando richiesto. Rapidamente ho notato che boost stava eseguendo il tracciamento degli oggetti per indirizzo di memoria, quindi il primo problema era che diversi oggetti nel tempo che condividevano lo stesso indirizzo di memoria venivano salvati come lo stesso oggetto. Questo può essere risolto utilizzando la seguente macro nella classe derivata richiesta:Serializzazione di classe derivata senza il monitoraggio della classe in Boost (C++)

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

Questo funziona bene, ma ancora una volta, quando la classe base non è astratta, la classe di base non è serializzato correttamente. Nell'esempio seguente, il metodo di serializzazione della classe base viene chiamato una sola volta con il primo oggetto. Di seguito, boost presuppone che questo oggetto sia stato serializzato prima sebbene l'oggetto abbia un tipo differente.

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

Quando si esegue questo pezzo di codice, il risultato è il seguente:

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

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

Quindi la classe di base è solo essere serializzato una volta, anche se la classe derivata ha esplicitamente la bandiera track_never. Esistono due soluzioni alternative per correggere questo comportamento. Il primo è quello di fare astrazione della classe base con un metodo virtuale puro e chiamando la macro BOOST_SERIALIZATION_ASSUME_ABSTRACT(Aclass), e la seconda è di mettere il flag track_never anche nella classe base (commentato nel codice).

Nessuna di queste soluzioni soddisfa i miei requisiti, dal momento che voglio eseguire in futuro serializzazioni puntuali dello stato del sistema, che richiederebbero funzionalità di tracciamento per un determinato DClass che estende A (non B o C), e anche l'AClass dovrebbe non essere astratto.

Eventuali suggerimenti? C'è un modo per chiamare esplicitamente il metodo di serializzazione della classe base evitando la funzione di tracciamento nella classe base (che è già stato disabilitato nella classe derivata)?

+0

Può fare in modo che l'amplificazione presuma che A sia virtuale anche se non lo è (voglio dire, basta decommentare la riga 'BOOST_SERIALIZATION_ASSUME_ABSTRACT (Aclass)' e provare a compilare)? – Synxis

risposta

2

Dopo un po 'più da vicino per aumentare :: serializzazione Sono anche convinto che non vi è una soluzione semplice per la vostra richiesta. Come già accennato, il comportamento di tracciamento per la serializzazione è dichiarato su una classe in base alla classe con BOOST_CLASS_TRACKING. Questa informazione globale const è da interpretare nel tracciamento del metodo virtuale dalla classe oserializer.

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

Poiché si tratta di una classe template è possibile creare un'istanza in modo esplicito questo metodo per le classi.

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(); 
    } 

}}} 

Ora si può provare a es avere qualcosa come una variabile globale e modificare il comportamento di monitoraggio di volta in volta. (E.g a seconda di quale classe derivata viene scritta nell'archivio.) Questo sembra funzionare per "Serializzare" ma "Deserializzare" piuttosto che generare un'eccezione. La ragione di ciò è che lo stato di "tracciamento" per ogni classe è solo scritto nell'archivio. Pertanto la deserializzazione si aspetta sempre i dati per AClass se si legge BClass o CClass (in leasing se il primo tentativo di scrittura per AClass era con tracciamento disabilitato).

Una possibile soluzione potrebbe essere l'uso del parametro flags nel metodo tracking(). Questo parametro rappresenta i flag con cui viene creato l'archivio, predefinito "0".

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

Le bandiere di archivio sono dichiarati in 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 sembra al momento essere supportato, ma ora è possibile aggiungere questo comportamento per il monitoraggio.

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

Ora è possibile in locazione decidere per diversi archivi se AClass deve essere monitorato o meno.

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

E queste sono le modifiche nell'esempio.

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

}}} 

Questo potrebbe non essere quello che stai cercando. Ci sono molti più metodi che potrebbero essere adattati con una propria implementazione. O devi derivare la tua classe di archivio.

+0

Sembra un buon approccio! Penso che potrebbe soddisfare le mie esigenze. Fammi controllare e testare la tua risposta in modo più dettagliato domani. Te lo dirò di nuovo. Grazie! –

+0

@alvarolb Sono un po 'curioso. Ha funzionato per te? –

+0

Infine mi prendo del tempo per testare il tuo approccio, ma sembra non funzionare. Appare un errore nel metodo di tracciamento virtuale, poiché non si trova all'interno dell'ambito della classe oserializer. "dichiarazione di classe esterna virtuale". E non posso modificare le librerie di boost :( –

2

In definitiva il problema sembra essere che un archivio boost::serialization rappresenta lo stato in un singolo punto nel tempo e si desidera che l'archivio contenga lo stato che è stato modificato, ovvero i puntatori che sono stati riutilizzati. Non penso ci sia un semplice flag boost::serialization che induca il comportamento che desideri.

Tuttavia, penso che ci siano altre soluzioni alternative che potrebbero essere sufficienti. È possibile incapsulare la serializzazione per una classe nel proprio archivio e quindi archiviare l'incapsulamento. Cioè, è possibile implementare la serializzazione per B come questo (si noti che si deve dividere serialize() in save() e 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() 

Poiché ogni istanza di B viene serializzato in un proprio archivio, A è efficacemente non monitorati perché esiste un solo riferimento per archivio di B. Questo produce:

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 

Un potenziale opposizione a tale tecnica è l'overhead di incapsulamento stoccaggio. Il risultato del programma di test originale è di 319 byte mentre il programma di test modificato produce 664 byte. Tuttavia, se gzip viene applicato a entrambi i file di output, le dimensioni sono 113 byte per l'originale e 116 byte per la modifica.Se lo spazio è un problema, consiglierei di aggiungere la compressione alla serializzazione esterna, che può essere facilmente eseguita con boost::iostreams.

Un'altra possibile soluzione è estendere la durata delle istanze alla durata dell'archivio in modo che i puntatori non vengano riutilizzati. È possibile farlo associando un contenitore di istanze shared_ptr all'archivio o allocando le istanze da un pool di memoria.

+0

Aggiunta una nota sul sovraccarico di archiviazione dell'incapsulamento di un archivio. – rhashimoto

+0

Grazie ... ma questa soluzione complica il processo di serializzazione e ne aumenta anche le dimensioni. Non posso anche estendere la durata dei puntatori poiché il sistema è destinato a funzionare a tempo indeterminato. Ad ogni modo, grazie per il tuo aiuto! –

Problemi correlati