Re: [boost] serialization: review

Thorsten Ottosen wrote:
Why isn't there a base class for all archieves? I think that would be useful
with class hierachies...for example, I need to implement two function and cannot use & anymore. Consider this
struct mammal : boost::noncopyable { private: friend class boost::serialization::access; virtual void do_serialize( boost::archive::text_oarchive& ar, unsigned version ) = 0; virtual void do_serialize( boost::archive::text_iarchive& ar, unsigned version ) = 0; template< class Archive > void serialize( Archive & ar, unsigned version ) { do_serialize( ar, version ); } public: virtual const std::string& name() const = 0; virtual unsigned age() const = 0; virtual ~mammal() {} };
I don't understand the need for this. Given that mammal is an abstract base class with no state variables of its own, it needs no serialization at all. Just declare it as abstract using BOOST_IS_ABSTRACT(mammal). Only the serialization of the derived classes need be specified.
I can't figure out exactly how the library save/loads pointers when these cannot be allocated on the stack.
Please expand some more on this.
a.. What is your evaluation of the documentation?
I miss more specific type requirements. For example, is it necessary to have a default constructor? Is it necessary to be stack-allocable? Must the destructor be public?
Good point.
I did try to extend the library to use my new smart containers; I was almost successful.
I would love to hear more about this. I would hope that it would easy to be totally successful.
I would like to see the focus shift from vector<T*> towards vector< shared_ptr<T> > and ptr_vector<T>. using T* array[sz] or vector<T*> is inherently unsafe if the pointers are heap-allocated.
I believe that enforcing this is outside my role as a library creator.
I tried to extend the library to support my smart containers, but I couldn't figure out what I did wrong. I think I might have saved them correctly, but I cannot figure out to load them. However, I do think that is a problem that can be solved together with Robert. (Robert, you can find the ptr_vector in the sandbox. If you could cast a glance on the code and tell me how the body of load_collection should look like, it would be great.)
I'll look at it. Thorsten
Some general nitpicks/comments (with these comments my test works on vc71, como4.3.3, g++3.3.1:
1. have you planned to use concept checks
I'm intrigued with concept checks for something like this but I havn't had the time to spend on it.
2. documentation: can the width of the html be made adaptive to the window width? The right window must be scrolled to see it's contents which is annoying.
This drives me crazy also. Unfortunately, I haven't been able to figure out what causes this. I believe it's a Microsoft IE issue - but who knows. I would be grateful to anyone can explain to me how to fix this. 3. const members uses a const-cast to re-init them. AFAICT, this is undefined behavior (cmp 7.1.5.1). I guess the need for serialization is just another reason for not having const members.
this has come up from time to time. I've tried to promote the view that const members shouldn't be serialized but I couldn't convince everyone. I consider it outside my role as a library developer to try to enforce this.
4. AFAICR, the use of placement new require proper alignment, so the object must eg. be heap-allocated. Is that guaranteed?
I believe this has been addressed. Objects allocated on the stack use aligned storage to handle this.
5. I think the exception code should be returned by a function: exception_code code() const; this is how eg. the regex_error exception in tr1 will do it.
6. remove trailing comma in archive_exception.hpp: "unregistered_cast // base - derived relationship not registered with"
OK - though I don't think its an error.
7. archive/detail/basic_oarchive.hpp includes itself
Whoops.
8. is_abstract.hpp: there is something wierd about the is_abstact struct. uncommented the first part to be able to compile.
I don't see this.
9. serialization/serialization.hpp: why all the static casts?
template<class Archive, class T> inline void serialize( Archive & ar, T & t, const BOOST_PFTO unsigned int file_version ){ access::serialize(ar, t, static_cast<const unsigned int>(file_version)); }
AFAICT, they should be removed. Comeau has the follwing warning: type qualifier is meaningless on cast type
They wouldn't be there except they provide a work around for compilers which do not support Partial Template Function Specialization. On conforming compilers, the BOOST_PFTO resolves to nothing so the cast wouldn't be necessary. So commeau is correct. I don't know how much effort I want to invest to suppress benign warnings for specific compilers.
10. nvp.hpp: line 50: ar & t.value(); There is on t-parameter.
OK - interesting the default implementation was never instantiated.
11. /archive/detail/oserializer.hpp", line 244: NULL reference is not allowed return * static_cast<const basic_pointer_oserializer *>(NULL); ^ This not yet legal C++ (but the committee is changing that) use a static member and return that instead.
Also, why all the static_casts?
In this case the reason is that when handling issues regarding preamble, object-id, class id etc. the pointer to the object is handled as void * . This permits the archive library to be pre-compiled and not have to be recompiled for every instance. When such a void pointer is passed back to a specific archive-type combination it has to be case back to the orginal type to permit the calling of type serialization functions. This is really a result reconciliation of conflicting goals. a) serialization of pointers abstract and otherwise b) serialization of pointers in a non-intrusive manner c) limit pointless instantiation. d) not require that user classes import headers related solely to implementation. For pointers this has generated a design which takes the typed pointer converts it to void pointer, handles preamble and book keeping, converts it back to typed pointer and (de)serializes the data. The requirement of non-intrusiveness means we can't just pass around a base class pointer - hence the need for casting.
12. two-phase lookup problems: /boost/archive/basic_text_oarchive.hpp", line 65: error #20: identifier "This" is undefined archive::save(* This(), t); ^ Solution: prefix base class functions with this->.
I'm not sure I understand this but I don't see it doing any harm. If this is a problem, the following would make more sense to me. archive::save(* common_oarchive<Archive>::This(), t);
remark: why is this->This()->XX() necessary? this->xx() should do it.
Hmm - I don't see this. save(Archive & ar, T t) is implemented in a more derived class. Common_oarchive<Archive> rededirects it downward to that class. I would expect this->xx() to give and undefined error. I could use archive::save(* static_cast<Archive *>(this)t); though I'm not sure you would find that anymore appealing.
13: archive/text_oarchive.hpp, similar lookup problems. this->newtoken(), this->delimiter = this->none;
as before - I don't see a problem but changing it doesn't hurt anything.
14: /archive/detail/basic_iarchive.hpp", line 46: error #20: identifier "basic_iarchive_impl" is undefined boost::scoped_ptr<basic_iarchive_impl> pimpl; ^ solution: remember to forward declare basic_iarchieve_impl. Do the same for basic_oarchieve_impl.
OK - I had thought that using friend class made that unnecessary.
15: /archive/detail/archive_pointer_iserializer.hpp", line 42: error #284: (+detail/iserializer.hpp", line 240:) NULL reference is not allowed return *static_cast<const basic_iserializer *>(NULL); ^ Solution: use a static member again.
16: two-phase lookup problems in archive/basic_text_iarchive.hpp", Solution: prefix by this->.
17. when you save_collection_impl.hpp (eg in vector.hpp) there is no need to specify template arguments again since they will be deduced.
I presume you mean that: template<class Archive, class U, class Allocator> inline void save( Archive & ar, const std::vector<U, Allocator> &t, const unsigned int file_version ){ stl::save_collection<Archive, std::vector<U, Allocator> >( ar, t ); } Can be replaced with: template<class Archive, class U, class Allocator> inline void save( Archive & ar, const std::vector<U, Allocator> &t, const unsigned int file_version ){ stl::save_collection<Archive, std::vector>( ar, t ); } But what about: template<class Archive, class U, class Allocator> inline void load( Archive & ar, std::vector<U, Allocator> &t, const unsigned int file_version ){ stl::load_collection< Archive, std::vector<U, Allocator>, stl::archive_input_seq<Archive, std::vector<U, Allocator> >, stl::reserve_imp<std::vector<U, Allocator> > >(ar, t); } This can't be replaced with: template<class Archive, class U, class Allocator> inline void load( Archive & ar, std::vector<U, Allocator> &t, const unsigned int file_version ){ stl::load_collection< Archive, std::vector stl::archive_input_seq<Archive, std::vector>, stl::reserve_imp<std::vector> >(ar, t); } Isn't a little bit of redundancy OK giving the alternative of having to keep the details of deducibility in one's brain?
18. implementation of save_collection(). you cannot use unsigned int count portably.
I don't see this.
Instead you should use the new collection traits (reviewed after this review) as follows:
typename boost::size_type_of< Collection >::type count = size( s );
then the code will also work for iterator views and arrays. Similar comments apply to load_collection_impl.hpp
I wanted to make the library buildable under 1.31 .
19. missing #include <boost/serialization/nvp.hpp> in serialization.hpp
Whoops - thanks. Robert Ramey

Hi Robert, I have some more stupid questions. | Thorsten Ottosen wrote: | | > Why isn't there a base class for all archieves? I think that would be | useful | | > with class hierachies...for example, I need to implement two function and | > cannot use & anymore. Consider this | I don't understand the need for this. Given that mammal is an abstract base | class with no state variables of its own, it needs no serialization at all. Ok, but could I have implemented it by having just one function in the abstract base class which seriaized by calling virtual functions, eg, name() and age()? | Just declare it as abstract using BOOST_IS_ABSTRACT(mammal). Only the | serialization of the derived classes need be specified. Can BOOST_IS_ABSTRACT be avoided? Maybe some type-traits can figure this out? In struct whale : public mammal { friend class boost::serialization::access; whale(); whale( const std::string& name, unsigned age ); virtual const std::string& name() const; virtual unsigned age() const; private: template<class Archive> void serialize(Archive &ar, const unsigned int version){ ar & boost::serialization::base_object<mammal>(*this); ar & name_ & age_ ; } std::string name_; unsigned age_; }; can the line "ar & boost::serialization::base_object<mammal>( *this )" be put in a base-class like this mammal::serialize( Archive& ar, unsigned version ) { ar & boost::serialization::base_object<mammal>(*this); do_serialize( ar, version ); // virtual function that does the rest } to remove some redundancy? | > I can't figure out exactly how the library save/loads pointers when these | > cannot be allocated on the stack. | | Please expand some more on this. I think you explain more about it later. Basically, I couldn't figure out the scheme with void*s and cast etc. | I would love to hear more about this. I would hope that it would easy to be | totally successful. it is now given you showed me how :-) In effect, once the container supports serialization, a class can be supported in four lines. If you have not got an example of class hierarchies, then maybe it should be added. | > I would like to see the focus shift from vector<T*> towards | > vector< shared_ptr<T> > and ptr_vector<T>. using T* array[sz] or | > vector<T*> is inherently unsafe if the pointers are heap-allocated. | | I believe that enforcing this is outside my role as a library creator. ok, but I think you at least should point out very clearly the problems in it. Some non-experinced programmer might do what he sees in documentation; therefore examples should encourage good style. | 3. const members uses a const-cast to re-init them. AFAICT, this is | undefined behavior (cmp 7.1.5.1). I guess the need for serialization is just | another reason for not having const members. | | > this has come up from time to time. I've tried to promote the view that | > const members shouldn't be serialized but I couldn't convince everyone. | > I consider it outside my role as a library developer to try to enforce | > this. my point was that the examples given give undefined behavior so that solution cannot be used.Unless there is something I overlooked. | > 4. AFAICR, the use of placement new require proper alignment, so the | > object must eg. be heap-allocated. Is that guaranteed? | | I believe this has been addressed. Objects allocated on the stack use | aligned storage to handle this. yes, neat. | > 6. remove trailing comma in archive_exception.hpp: "unregistered_cast // | > base - derived relationship not registered with" | | OK - though I don't think its an error. Comeau call's it non-standard. | > 8. is_abstract.hpp: there is something wierd about the is_abstact struct. | > uncommented the first part to be able to compile. | | I don't see this. // so default to false until the above is really fixed namespace boost { \ template<class T> struct is_abstract { typedef mpl::false_ type; BOOST_STATIC_CONSTANT(bool, value = is_abstract::type::value); }; } // namespace boost what is the '\' doing there? I just uncommented the whole thing. | > 9. serialization/serialization.hpp: why all the static casts? | > AFAICT, they should be removed. Comeau has the follwing warning: type | > qualifier is meaningless on cast type | | They wouldn't be there except they provide a work around for compilers which | do not support Partial Template Function Specialization. On conforming | compilers, the BOOST_PFTO resolves to nothing so the cast wouldn't be | necessary. So commeau is correct. I don't know how much effort I want to | invest to suppress benign warnings for specific compilers. ok, we should consider either to remove warnings when using bjam or come up with something. | > 12. two-phase lookup problems: | > /boost/archive/basic_text_oarchive.hpp", line 65: error #20: | > identifier "This" is undefined | > archive::save(* This(), t); | ^ | > Solution: prefix base class functions with this->. | | I'm not sure I understand this but I don't see it doing any harm. If this | is a problem, the following would make more sense to me. when you access something in a dependent base class, Base, one must prefix function with either this-> or Base::. The first preserves virtuality, the second don't. Similarly, typedefs need to be prefixed by Base::. | archive::save(* common_oarchive<Archive>::This(), t); the problem is that This() is defined in a dependent base class. | > 13: archive/text_oarchive.hpp, similar lookup problems. this->newtoken(), | > this->delimiter = this->none; | | as before - I don't see a problem but changing it doesn't hurt anything. it makes all the difference on comforming compilers. | > 14: /archive/detail/basic_iarchive.hpp", line 46: error #20: | > identifier "basic_iarchive_impl" is undefined | > boost::scoped_ptr<basic_iarchive_impl> pimpl; | ^ | >solution: remember to forward declare basic_iarchieve_impl. Do the same for | >basic_oarchieve_impl. | | OK - I had thought that using friend class made that unnecessary. I'm not sure who's right here, but at least it will make it work with one more compiler. | > 17. when you save_collection_impl.hpp (eg in vector.hpp) there is no need | > to specify template arguments again since they will be deduced. | | I presume you mean that: | | template<class Archive, class U, class Allocator> | inline void save( | Archive & ar, | const std::vector<U, Allocator> &t, | const unsigned int file_version | ){ | stl::save_collection<Archive, std::vector<U, Allocator> >( | ar, t | ); | } | | Can be replaced with: | | template<class Archive, class U, class Allocator> | inline void save( | Archive & ar, | const std::vector<U, Allocator> &t, | const unsigned int file_version | ){ | stl::save_collection<Archive, std::vector>( | ar, t | ); | } yes. | But what about: | | template<class Archive, class U, class Allocator> | inline void load( | Archive & ar, | std::vector<U, Allocator> &t, | const unsigned int file_version | ){ | stl::load_collection< | Archive, | std::vector<U, Allocator>, | stl::archive_input_seq<Archive, std::vector<U, Allocator> >, | stl::reserve_imp<std::vector<U, Allocator> > | >(ar, t); | } | | This can't be replaced with: | | template<class Archive, class U, class Allocator> | inline void load( | Archive & ar, | std::vector<U, Allocator> &t, | const unsigned int file_version | ){ | stl::load_collection< [snip, correction to no list here] | >(ar, t); | } no, but maybe with this | stl::archive_input_seq<Archive, std::vector>, | stl::reserve_imp<std::vector> by arranging the order of the template parameters. | Isn't a little bit of redundancy OK giving the alternative of having to keep | the details of deducibility in one's brain? but you already know the types that will be deduced from the outer function. | > 18. implementation of save_collection(). you cannot use unsigned int count | > portably. | | I don't see this. | | > Instead you should use the new collection traits (reviewed after | > this review) as follows: | | > typename boost::size_type_of< Collection >::type count = size( s ); | | > then the code will also work for iterator views and arrays. Similar | > comments apply to load_collection_impl.hpp | | I wanted to make the library buildable under 1.31 . ok, then do typename Collection::size_type count = s.size(); but don't assume Collection::size_type will be unsigned int. br Thorsten
participants (2)
-
Robert Ramey
-
Thorsten Ottosen