Boost logo

Boost :

From: Joaquín Mª López Muñoz (joaquin_at_[hidden])
Date: 2005-09-22 12:46:09


David Abrahams ha escrito:

> Joaquín Mª López Muñoz <joaquin_at_[hidden]> writes:
>
> > In my serialization stuff for Boost.MultiIndex I actually have a
> > serializable type that does not conform to the equivalence rule. Its
> > layout kinda looks like:
> >
> > template<typename Value>
> > struct node
> > {
> > value v;
> >
> > template<class Archive>
> > void serialize(Archive& ar,const unsigned int)
> > {
> > // do nothing
> > }
> > }
> >
> > I use this weird construct to make node trackable, but no contents
> > information is dumped to the archive (that is taken care of somewhere
> > else in the program). In case you're curious, this arises in connection
> > with serialization of iterators.
>
> I can't imagine why you'd need that; a hint would help me to
> understand better.

It's a little hard to grasp; this particular issue took me literally
weeks of thinking, but I'll try to explain it a little more. Beware this
doesn't add much to our current discussion, you might want to skip:

Suppose we are implemeting serialization for a custom container:
save and load are straight enough:

class container{
  save(...)
  {
    for(const_iterator it=begin,it_end=end();it!=it_end;++it){
      ar<<*it;
    }
  }
  load(...)
  {
    clear();
    for(iterator it=begin,it_end=end();it!=it_end;++it){
      value_type v;
      ar>>v;
      push_back(v);
      ar.reset_object_address(&v,&back());
    }
  }
};

Now we want to add serialization for iterators. One ugly way would be
as follows:

class iterator{
  save(...){
    ar<<&(operator*()); // save pointer to element
  }
  load(...){
    value_type* pv;
    ar>>pv;
    node* pn;
    // cast from pv to pn: possibly nonportable.
    assign(pn);
  }
};

This is potentially nonportable and, besides, won't work for nontracked
value_types.
What we want is to archive pointers to the internal nodes, rather than the values:

class iterator
{
  save(...){
    ar<<node_ptr;
  }
  load(...){
    node* pn;
    ar>>pn;
    node_ptr=pn;
  }
};

But for this to work, nodes must be serialized first so that they can be tracked
later.

class container{
  save(...)
  {
    for(const_iterator it=begin,it_end=end();it!=it_end;++it){
      ar<<*it; // save value
      ar<<*it.node_ptr; // save node
    }
  }
  load(...)
  {
    clear();
    for(iterator it=begin,it_end=end();it!=it_end;++it){
      value_type v;
      ar>>v;
      push_back(v);
      ar.reset_object_address(&v,&back());
      ar>>*(--end()).node_ptr; // "load" node
    }
  }
};

That's the purpose of node serialization stuff. The implementation does nothing
except signalling Boost.Serialization where later node pointers must point to.

> Are you saying there's no sense in which a deserialized node<T> will
> be equivalent to the one that has been serialized?

In a sense, restored nodes are equivalent to their originals, but I'd say
it is a convoluted sense.

> Well, the do-nothing Saving Archive doesn't have to have a
> corresponding Loading Archive. It's the notion of correspondence that
> we're concerned here, not necessarily an intrinsic property of Archives.

Correct, I got it wrong there.

> >> What if the user serializes an aggregate struct X containing two ints?
> >> Is the corresponding input archive required to be able to read two
> >> ints as part of reading an X?
> >
> > Not only that: X::save is actually *required* to load those two
> ^^^^ ^^^^
> ??

I meant X::load, sorry.

> > Note that foo::save only loads the first int. The program outputs

I meant foo::load, again.

>
> >
> > y0.a=1
> > y1.a=2
> >
> > which is incorrect (y1.a should be 3), so serialization of foo is not
> > correctly implemented. For XML archive types my hunch is that the
> > program would throw.
>
> Okay, so it would be sufficient to add
>
> int x;
> ar >> x;
>
> to foo::load, right? Otherwise it seems you're treading back into the
> domain of equivalence.

Correct.

> > * An input archive iar is compatible with an output archive oar if
> > 1. iar allows a sequence of >> ops matching the corresponding << ops
> > made upon oar (matching defined in terms of types involved and
> > nesting depth of the call.)
>
> Is the nesting depth of the call really relevant?

Ummm... No, we can drop that: the nesting thing is redundant with
the requirement on serializable types about matching of << and >> ops.
On the other hand, XML archives do enforce the nesting abidance,
but this is more of an implementation artifact.

> > 2. For primitive serialization types, the restored copies are equivalent
> > to their original (expand on this, specially with respect to pointers.)
> > * A type T is serializable if it is primitive serializable or else it defines
> > the appropriate serialize (load/save) function such that the sequence
> > of >> ops in load() match the << ops in save().
> >
> > [This is not a requirement] For each serializable type, the implementor
> > can define "equivalence" in terms of its constituent types. For instance,
> > for std::vector:
> >
> > Given a std::vector<T> out, where T is serializable, and a restored copy in,
> > then in(i).size()==out(i).size() and each in(i)[j] is a restored copy of
> > out(i)[j].
>
> I don't think this latter part is worth much. I think it might be
> worth defining an EquivalentSerializable concept, though.

One can do the following:

Let T be a serializable type and Pred an associated equality predicate inducing
an equivalence relationship on T. Then T is said to be EquivalentSerializable
(under Pred) if

  p(x,y)==true

for all p of type Pred, and x and y of type T such that y is a restored
copy of x.

This leaves to the implementor of an UDT the open task of giving the appropriate
associated equality predicate (by default we can assume std::equal_to). Then we
can rewrite the postcondition on std::vector as

if T is EquivalentSerializable under Pred, std::vector<T> is
EquivalentSerializable.

(The statement is a little more complex if we take a Pred other than the default.)
Of course, this EquivalentSerializable concept does not save us the task of
first providing archive compatibilty and Serializable concepts the hard way, and
it is only applicable intraprogram.

Does this sound good to you?

Joaquín M López Muñoz
Telefónica, Investigación y Desarrollo


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk