Re: [Boost-users] Library Interface Design

-----Original Message----- From: boost-users-bounces@lists.boost.org [mailto:boost-users- bounces@lists.boost.org] On Behalf Of Scott Meyers Sent: Tuesday, September 12, 2006 1:09 AM To: boost-users@lists.boost.org Subject: [Boost-users] Library Interface Design
I've always been a big fan of the "don't let users create objects they can't really use" philosophy. So, in general, I don't like the idea of "empty" objects or two-phase construction. It's too easy for people to make mistakes.
[Nat] I try to design for single-phase construction myself. The aberrant use case that I've bumped into is deserialization. Intrusive serialization can preserve single-phase construction -- but what if the class designer didn't anticipate the need to serialize? I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)

Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments. -- Loïc

Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid. See the "Non-Default Constructors" part of the serialization library documentation for details. The problem still might be that you don't have the right data available while deserializing your object.

Oliver Mutz a écrit :
Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid. See the "Non-Default Constructors" part of the serialization library documentation for details.
This pertains to deserializing pointers to instances of classes with no default constructor. Is does not mean anything concerning deserialization of instances. Those need to be constructed before being deserialized to. -- Loïc

Loïc Joly wrote:
Oliver Mutz a écrit :
Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.) Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid. See the "Non-Default Constructors" part of the serialization library documentation for details.
This pertains to deserializing pointers to instances of classes with no default constructor. Is does not mean anything concerning deserialization of instances. Those need to be constructed before being deserialized to.
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data. date_time had serialization code before it had default constructors for dates. To this day it doesn't depend on default constructors in the serialization code. Jeff

Jeff Garland a écrit :
Loïc Joly wrote:
Oliver Mutz a écrit :
Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid. See the "Non-Default Constructors" part of the serialization library documentation for details.
This pertains to deserializing pointers to instances of classes with no default constructor. Is does not mean anything concerning deserialization of instances. Those need to be constructed before being deserialized to.
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake. What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version. In my C++ code, when I use uninitialized or "dummy-initialized" object, this is most of the time because I'm going to read a stream into the object, or deserialize the object. -- Loïc

Loïc Joly wrote:
Jeff Garland a écrit :
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep. So in some case this is easy and in some cases not. But in some of the 'hard cases' you wouldn't typically serialize the objects anyway. I'd argue the EventLogger case is not the sort of object that would ever by serialized. This is compared to dates, times, strings, collections, and other objects that will frequently be serialized.
In my C++ code, when I use uninitialized or "dummy-initialized" object, this is most of the time because I'm going to read a stream into the object, or deserialize the object.
Yep. Jeff

Jeff Garland
Loïc Joly wrote:
Jeff Garland a écrit :
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed: http://boost.org/libs/serialization/doc/serialization.html#constructors: template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); } One phase construction. -- Dave Abrahams Boost Consulting www.boost-consulting.com

On 9/13/06, David Abrahams
Jeff Garland
writes: Loïc Joly wrote:
Jeff Garland a écrit :
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed:
http://boost.org/libs/serialization/doc/serialization.html#constructors:
template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); }
One phase construction.
I poked around in the link you posted, but I don't see any examples of a my_class getting serialized into. Where does t come from? IOW, how do I call this without passing dummy information into a my_class object? I'm sure I'm missing something, but all I can envision is a reinterpret_cast of a void*/malloc. Or two one-phase constructions.

"Thomas Matelich"
On 9/13/06, David Abrahams
wrote: Jeff Garland
writes: Loïc Joly wrote:
Jeff Garland a écrit :
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed:
http://boost.org/libs/serialization/doc/serialization.html#constructors:
template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); }
One phase construction.
I poked around in the link you posted, but I don't see any examples of a my_class getting serialized into.
That's exactly what's happening above.
Where does t come from?
It's memory allocated for you by the serialization library I suppose.
IOW, how do I call this without passing dummy information into a my_class object?
t isn't passed into the my_class; it's just raw memory with suitable alignment.
I'm sure I'm missing something, but all I can envision is a reinterpret_cast of a void*/malloc. Or two one-phase constructions.
new(t) X(m) constructs a new X object in the memory at t, passing m as the one ctor argument. Does that help? -- Dave Abrahams Boost Consulting www.boost-consulting.com

On 9/13/06, David Abrahams
"Thomas Matelich"
writes: On 9/13/06, David Abrahams
wrote: Jeff Garland
writes: Loïc Joly wrote:
Jeff Garland a écrit :
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed:
http://boost.org/libs/serialization/doc/serialization.html#constructors:
template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); }
One phase construction.
I poked around in the link you posted, but I don't see any examples of a my_class getting serialized into.
That's exactly what's happening above.
Where does t come from?
It's memory allocated for you by the serialization library I suppose.
IOW, how do I call this without passing dummy information into a my_class object?
t isn't passed into the my_class; it's just raw memory with suitable alignment.
I'm sure I'm missing something, but all I can envision is a reinterpret_cast of a void*/malloc. Or two one-phase constructions.
new(t) X(m)
constructs a new X object in the memory at t, passing m as the one ctor argument.
Does that help?
Not really. I get the new(t) part, I guess I'd just like to see to client code, likely due to my denseness. Maybe I'll go look in the serialization unittests.

On 9/13/06, Thomas Matelich
On 9/13/06, David Abrahams
wrote: "Thomas Matelich"
writes: On 9/13/06, David Abrahams
wrote: Jeff Garland
writes: Loïc Joly wrote:
Jeff Garland a écrit :
> Oliver is correct -- serialization does not require default constructors for > the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed:
http://boost.org/libs/serialization/doc/serialization.html#constructors:
template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); }
One phase construction.
I poked around in the link you posted, but I don't see any examples of a my_class getting serialized into.
That's exactly what's happening above.
Where does t come from?
It's memory allocated for you by the serialization library I suppose.
IOW, how do I call this without passing dummy information into a my_class object?
t isn't passed into the my_class; it's just raw memory with suitable alignment.
I'm sure I'm missing something, but all I can envision is a reinterpret_cast of a void*/malloc. Or two one-phase constructions.
new(t) X(m)
constructs a new X object in the memory at t, passing m as the one ctor argument.
Does that help?
Not really. I get the new(t) part, I guess I'd just like to see to client code, likely due to my denseness. Maybe I'll go look in the serialization unittests.
Ok, so I took a look at test_non_default_ctor*.cpp (from 1.33.1). I'm enlightened and confused. It appears it will allocate memory for you, or you can do two one-phase constructions if you want your object on the stack. I do find this test odd: { //... A a(4); ia >> BOOST_SERIALIZATION_NVP(a); A *pa1; ia >> BOOST_SERIALIZATION_NVP(pa1); BOOST_CHECK_MESSAGE(pa1 == &a, "Copy of pointer not correctly restored"); A *pa2; ia >> BOOST_SERIALIZATION_NVP(pa2); BOOST_CHECK_MESSAGE(pa2 != &a, "Pointer not correctly restored"); //... } Mainly because I don't know why one would want/expect this behavior. If someone has a short answer, I'd appreciate it, otherwise I'll figure it out if I ever use Boost.Serialization. Oh, and since I didn't change the subject, I agree with the others on favoring one-phase construction.

David Abrahams ha escrito:
Jeff Garland
writes: Loïc Joly wrote:
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
Yep.
Nope. Sorry to be blunt, but I just want to make absolutely sure this isn't missed:
http://boost.org/libs/serialization/doc/serialization.html#constructors:
template<class Archive> inline void load_construct_data( Archive & ar, my_class * t, const unsigned int file_version ){ // retrieve data from archive required to construct new instance int m; ar >> m; // invoke inplace constructor to initialize instance of my_class ::new(t)my_class(m); }
One phase construction.
I am not sure. I'm a little puzzled about the intended purpose of
load_construct_data, as the docs and the code of Boost.Serialization seem to
suggest that it is used for two incompatible tasks:
a) to construct a dummy object prior to deserialization itself, thus
providing a user-overridable hook in case default construction is not
appropriate.
b) to deserialize in place as explained on the bit you quote.
My hunch is that Robert started this with b) in mind but finally the facility
was kept playing a more humble role as a). Consider for instance, serialization
of pointers: currently, if an object instance is saved exclusively through
pointers, Boost.Serialization does the following when the first such
pointer is met
save_construct_data(ar,p); // S(0)
ar<<*p; // S(1)
and correspondingly at loading time
p=get unitialized storage;
load_construct_data(ar,p); // L(0)
ar>>*p; // L(1)
By default save_construct_data does nothing and load_construct_data
creates a default cted object, effectively leading to a two-phase construction
scenario. Now, if the user decides to override (save|load)_construct_data
for some class, say foo, so as to perform one-phase construction, we've got
the following:
S(0) saves foo contents
S(1) saves foo contents again
L(0) loads foo contents and creates a foo object with these.
L(1) loads foo contents again
leading to data duplication within the archive. This can be remedied by
also overriding foo::serialize so as to do nothing, but then foo would be
only serializable through pointers: serialization through instances
foo f(...)
...
ar<

Joaquín Mª López Muñoz
Am I missing something? I hope Robert has the time to comment on this.
Me too. If you're _not_ missing something, this looks like a serious design deficiency of which I wasn't aware. -- Dave Abrahams Boost Consulting www.boost-consulting.com

I am not sure. I'm a little puzzled about the intended purpose of load_construct_data, as the docs and the code of Boost.Serialization seem to suggest that it is used for two incompatible tasks:
a) to construct a dummy object prior to deserialization itself, thus providing a user-overridable hook in case default construction is not appropriate. b) to deserialize in place as explained on the bit you quote.
The intended purpose was to address de-serialization of objects that have no default constructor. See test_non_default_ctor.
My hunch is that Robert started this with b) in mind but finally the facility was kept playing a more humble role as a). Consider for instance, serialization of pointers: currently, if an object instance is saved exclusively through pointers, Boost.Serialization does the following when the first such pointer is met
save_construct_data(ar,p); // S(0) ar<<*p; // S(1)
and correspondingly at loading time
p=get unitialized storage; load_construct_data(ar,p); // L(0) ar>>*p; // L(1)
By default save_construct_data does nothing and load_construct_data creates a default cted object, effectively leading to a two-phase construction scenario. Now, if the user decides to override (save|load)_construct_data for some class, say foo, so as to perform one-phase construction, we've got the following:
S(0) saves foo contents S(1) saves foo contents again
Nope. save_construct_data saves ONLY that data needed for construction while ar << *p save that data NOT used in construction.
L(0) loads foo contents and creates a foo object with these. L(1) loads foo contents again
Nope - see above. I believe this is well illustrated by the test_non_default_ctor.cpp example previously cited. Note that in generally, the data required to construct an instance might not be part of the instance itself. This is also addressed by this api. I'm not sure, but I suspect this could be also useful in de-serialization of objects which contain references to other objects as references can only be set at object construction time. Tracking would still work. Robert Ramey
leading to data duplication within the archive. This can be remedied by also overriding foo::serialize so as to do nothing, but then foo would be only serializable through pointers: serialization through instances
foo f(...) ... ar<
would do nothing!!
The only way in which load_construct_data would have served purpose b) is if it had designed so that
1: the default implementation for load_construct_data was: load_construct_data(ar,p){ ::new(p)T(); ar>>*p; } 2: the default implementation for save_construct_data was: save_construct_data(ar,p){ ar<<*p; } 3: Through-pointer serialization only consisted of steps S(0) and L(0) and omitted L(1) and S(1)
With this design, one would be able to override load_construct_data in the manner explained in serialization.html#constructors while retaining normal through-instance serializiation. As it currently stands, my view is that (save|load)_construct_data is not fit for one-phase deserialization.
Am I missing something? I hope Robert has the time to comment on this.
Joaquín M López Muñoz Telefónica, Investigación y Desarrollo

Loïc Joly
Jeff Garland a écrit :
Loïc Joly wrote:
Oliver Mutz a écrit :
Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid. See the "Non-Default Constructors" part of the serialization library documentation for details.
This pertains to deserializing pointers to instances of classes with no default constructor. Is does not mean anything concerning deserialization of instances. Those need to be constructed before being deserialized to.
Oliver is correct -- serialization does not require default constructors for the types. It does require a constructed object prior to reading in the data.
Yes, you are right. My mistake.
What I meant is that for deserialisation, you still have a two phases construction: First, build your object with any mean available (if your object have only constructors with non-default parameters, this will probably imply building them with dummy parameters), then, in a second phase, override the member values by the serialized version.
No. Please see http://boost.org/libs/serialization/doc/serialization.html#constructors That's one phase. -- Dave Abrahams Boost Consulting www.boost-consulting.com

"Oliver Mutz"
Loïc Joly wrote:
Nat Goodspeed a écrit :
I haven't yet worked with a serialization framework smart enough to consider constructor arguments. (I haven't yet worked with the Boost Serialization library, either; forgive me if this is already a solved problem.)
Boost::serialization requires a constructor with no arguments.
That's not right I'm afraid.
And thank goodness.
See the "Non-Default Constructors" part of the serialization library documentation for details. The problem still might be that you don't have the right data available while deserializing your object.
Then you can't usefully construct it. What's the point in making a broken instance? -- Dave Abrahams Boost Consulting www.boost-consulting.com
participants (8)
-
David Abrahams
-
Jeff Garland
-
Joaquín Mª López Muñoz
-
Loïc Joly
-
Nat Goodspeed
-
Oliver Mutz
-
Robert Ramey
-
Thomas Matelich