[serialization] Serialization of derived classes
Hi there, I'm having problems when serializing derived classes from a
non-polymorphic base class. I have read the section "Pointers to
Objects of Derived Classes". But for some reasons I cannot make these
tricks to work in my application. I have created a small sample
application that shows my problem:
class base
{
public:
base() : _type( 0 ) {}
base( unsigned int type) : _type( type ) {}
unsigned int type() { return _type; }
private:
friend boost::serialization::access;
template< class ARCHIVE >
void serialize( ARCHIVE& ar, const unsigned int version )
{
ar & _type;
}
private:
unsigned int _type;
};
class derived1 : public base
{
public:
derived1() : base( 1 ), _value( 2.1 ) {}
private:
friend boost::serialization::access;
template< class ARCHIVE >
void serialize( ARCHIVE& ar, const unsigned int version )
{
ar & boost::serialization::base_object<base>( *this );
ar & _value;
}
private:
double _value;
};
class derived2 : public base
{
public:
derived2() : base( 2 ), _value( 8 ) {}
private:
friend boost::serialization::access;
template< class ARCHIVE >
void serialize( ARCHIVE& ar, const unsigned int version )
{
ar & boost::serialization::base_object<base>( *this );
ar & _value;
}
private:
short _value;
};
BOOST_CLASS_EXPORT( derived1 );
BOOST_CLASS_EXPORT( derived2 );
int _tmain(int argc, _TCHAR* argv[])
{
std::string data;
{
std::ostringstream archive_stream;
boost::archive::binary_oarchive archive( archive_stream );
derived2 d;
archive & d;
data = archive_stream.str();
}
{
// deserialize
std::string archive_data( &data[0], data.size() );
std::istringstream archive_stream( archive_data );
boost::archive::binary_iarchive archive( archive_stream );
// I don't know what derived class was serialized.
base* b;
archive & b;
if( b->type() == 1 )
{
derived1* d1 = static_cast
For this two work, the base class MUST be polymorphic. To fix this make one of your functions "virtual". A simple and common way to do this is to make the destructor virtual. Another good idea in my opinion is to make the base class abstract by setting all functions "=0". (you'll have to use BOOST_IS_ABSTRACT as well). This is not required for serializaton. But I believe it helps clarify the design pattern where the base class is an interface while the derived classes are specific implementations. In this case I find it helpful to make the constructors (and destructor) "protected:") as well. Robert Ramey Christian Henning wrote:
Hi there, I'm having problems when serializing derived classes from a non-polymorphic base class. I have read the section "Pointers to Objects of Derived Classes". But for some reasons I cannot make these tricks to work in my application. I have created a small sample application that shows my problem:
class base { public: base() : _type( 0 ) {} base( unsigned int type) : _type( type ) {}
unsigned int type() { return _type; } private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & _type; }
private:
unsigned int _type; };
class derived1 : public base { public: derived1() : base( 1 ), _value( 2.1 ) {}
private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & boost::serialization::base_object<base>( *this ); ar & _value; }
private:
double _value; };
class derived2 : public base { public: derived2() : base( 2 ), _value( 8 ) {}
private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & boost::serialization::base_object<base>( *this ); ar & _value; }
private:
short _value; };
BOOST_CLASS_EXPORT( derived1 ); BOOST_CLASS_EXPORT( derived2 );
int _tmain(int argc, _TCHAR* argv[]) { std::string data;
{ std::ostringstream archive_stream; boost::archive::binary_oarchive archive( archive_stream );
derived2 d; archive & d;
data = archive_stream.str(); }
{ // deserialize std::string archive_data( &data[0], data.size() ); std::istringstream archive_stream( archive_data ); boost::archive::binary_iarchive archive( archive_stream );
// I don't know what derived class was serialized. base* b; archive & b;
if( b->type() == 1 ) { derived1* d1 = static_cast
( b ); } else { derived2* d2 = static_cast ( b ); } } return 0; }
Any help is more than welcome.
Thanks ahead, Christian
Hi Robert, thanks for the quick answer. I went for the second idea and
made my base class abstract using a pure virtual member function.
Unfortunately, now I'm catching an unknown exception when trying to
deserialize.
It seems to me that the serializing lib still doesn't know that it's
deserializing an object of type derived2.
Here is what I'm doing right know:
#include
Actually - the de-serialization fails. I suspect this is due to he stream not being opened in binary mode - on both input and output. As a quick test - you can try a text stream. Investigate this. Robert Ramey Christian Henning wrote:
Hi Robert, thanks for the quick answer. I went for the second idea and made my base class abstract using a pure virtual member function. Unfortunately, now I'm catching an unknown exception when trying to deserialize.
I changed to text archives and they are failing as well. BTW, I'm
running VC2003 and boost 1.33.1. Is this sample program failing on
your side, as well?
Thanks,
Christian
On 10/23/06, Robert Ramey
Actually - the de-serialization fails.
I suspect this is due to he stream not being opened in binary mode - on both input and output. As a quick test - you can try a text stream. Investigate this.
Robert Ramey
Christian Henning wrote:
Hi Robert, thanks for the quick answer. I went for the second idea and made my base class abstract using a pure virtual member function. Unfortunately, now I'm catching an unknown exception when trying to deserialize.
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
Hmm - its failing for me as well, but I can't see why. I'm testing with HEAD but still it fails. The exception thrown suggests a failure to register d2 as an exportable type - or to find the registration. I'm still looking at it. Robert Ramey Christian Henning wrote:
I changed to text archives and they are failing as well. BTW, I'm running VC2003 and boost 1.33.1. Is this sample program failing on your side, as well?
Thanks, Christian
On 10/23/06, Robert Ramey
wrote: Actually - the de-serialization fails.
I suspect this is due to he stream not being opened in binary mode - on both input and output. As a quick test - you can try a text stream. Investigate this.
Robert Ramey
Christian Henning wrote:
Hi Robert, thanks for the quick answer. I went for the second idea and made my base class abstract using a pure virtual member function. Unfortunately, now I'm catching an unknown exception when trying to deserialize.
_______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
Christian Henning schrieb:
Hi Robert, thanks for the quick answer. I went for the second idea and made my base class abstract using a pure virtual member function. Unfortunately, now I'm catching an unknown exception when trying to deserialize.
It seems to me that the serializing lib still doesn't know that it's deserializing an object of type derived2.
Here is what I'm doing right know:
#include
#include #include #include #include class base { public: virtual void foo() =0; unsigned int type() { return _type; }
protected:
base() : _type( 0 ) {} base( unsigned int type) : _type( type ) {}
private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & _type; }
private:
unsigned int _type; };
BOOST_IS_ABSTRACT( base )
class derived1 : public base { public: derived1() : base( 1 ), _value( 2.1 ) {}
virtual void foo() {}
private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & boost::serialization::base_object<base>( *this ); ar & _value; }
private:
double _value; };
class derived2 : public base { public: derived2() : base( 2 ), _value( 8 ) {}
virtual void foo() {}
private:
friend boost::serialization::access;
template< class ARCHIVE > void serialize( ARCHIVE& ar, const unsigned int version ) { ar & boost::serialization::base_object<base>( *this ); ar & _value; }
private:
short _value; };
BOOST_CLASS_EXPORT( derived1 ); BOOST_CLASS_EXPORT( derived2 );
Hi Christian,
it should work if you change BOOST_CLASS_EXPORT to BOOST_CLASS_EXPORT_GUID
like this:
#include
Hi Christian, Nope, sorry, that's not it. I get still an unknown exception. I also thought the section "Derived Pointers" is for non-polymorphic base classes. In my sample I made it polymorphic as Robert sugessted. Do think it's possible to send me your working example? Thanks, Christian
"Christian Rössel"
Christian Henning schrieb:
Hi Robert, thanks for the quick answer. I went for the second idea and
...
BOOST_CLASS_EXPORT( derived1 ); BOOST_CLASS_EXPORT( derived2 );
Hi Christian,
it should work if you change BOOST_CLASS_EXPORT to BOOST_CLASS_EXPORT_GUID like this:
#include
... BOOST_CLASS_EXPORT_GUID(derived1, "derived1") BOOST_CLASS_EXPORT_GUID(derived2, "derived2")
Hmm, shouldn't base be exported as well? Jeff
Just tried that out of curiosity. It still fails. In general I'm not interested in base objects only derived objects from base. Christian
OK - figured it out. As usual something really obvious in retrospect.
The serialization library presumes that each data type loaded will
be the same type that was saved.
In this case we're saving a type d2 and loading a type base.
In saving a type d2 - it's presumed that the type loaded
will also be type d2 so there is no need to include type
information in the archive.
If the type being saved is base, the library "realizes" that
this could be either d2 or d1 and save a tag in the archive
accordingly. This tag is set by the BOOST_CLASS_EXPORT
macro.
The tip-off is that when the archive is rendered as text, no
type name tag appears. The following modification to your
test fixes the problem. (I made some other changes in the
course of tracking down the issue - they are beside the
point addressed here)
Robert Ramey
int main(int argc, char **argv[]){
std::string data;
{
std::ostringstream archive_stream;
{
boost::archive::text_oarchive archive( archive_stream );
// create an instance of derived2
const derived2 d;
// create a pointer of the above cast to the base class.
const base * const b = & d;
// now serialize through the base class pointer.
archive << b;
}
data = archive_stream.str();
}
{
// deserialize
std::istringstream archive_stream(data);
boost::archive::text_iarchive archive( archive_stream );
// I don't know what derived class was serialized.
// note that we're loading a base * - just the same as we saved !!!
base* b;
try{
archive >> b;
}
catch( std::exception e ){
std::cout << e.what() << std::endl;
}
if( b->type() == 1 ){
derived1* d1 = static_cast
Robert, thank you so much. Seems to me as a general rule of thumb that serialization is somewhat symmetric. I mean if you need to use a base pointer for deserialization you need to use a base pointer when serializing the object. Right? Thanks again, Christian
Christian Henning wrote:
Robert, thank you so much. Seems to me as a general rule of thumb that serialization is somewhat symmetric.
As far as the data items and types are concerned, its totally symmetric. This is the bedrock on which it is based. This is why: a) archives contain so little information - type/data symetry is presumed. b) versioning is important. archives don't keep much information about themselves. Much of the information is in the C++ data structures and thier serialization functions. c) its hard to edit archives independently of the serialization functions. d) .... some other thngs. The only asymmetry is in some cases loading is not a symmetrical operation to saving due to the nature of the data type. Also, once additional versions are introduced, loading and saving will not be symmetrical. That is, loading needs to load old versions - while saving only needs to save the current one.
I mean if you need to use a base pointer for deserialization you need to use a base pointer when serializing the object. Right?
In general if you save type T you better load type T. This is because a lot of the "magic" is implementing information about the type in the archive. Note that common error that is difficult to find is when the saving an loading of the data is not symmetrical. Example - saving x, y and loading x, y, z. The xml_?archives check for this but other archives - in the interest of speed - trust the programmer.
Thanks again, Christian
Robert Ramey
participants (4)
-
Christian Henning
-
Christian Rössel
-
Jeff F
-
Robert Ramey