Object graphs that have cycles in them are supported. A cycle occurs when an object point to other objects which point back to the first object. Such graphs are correctly restored upon deserialization.
For pointers to objects, there are two library behaviours that can be controlled by clients: the tracking of pointers, and the handling of pointers to polymorphic objects.
What does pointer tracking do? When enabled, it does two things:
It causes the pointer's data (ie, the object that is pointed at) to be stored only once in the database. After the first time that the object is encountered by the library, all other requests to save the same pointer will cause the library to write a database "pointer" to the same row of the same table as the first time the object was written.
It causes any pointers to the same object to receive the
same value when the data is retrieved from the database. If
p and q are two pointers
which have the same value (ie, point to the same object) and are
then serialized to the database, then upon loading the serialized
data, the same two pointers will end up pointing to the same
deserialized object data. For example, if p and
q have the address 0x1234 when they are stored,
and later on their values are deserialized into a new object which
has the address 0x5678, then p and
q will both have the value 0x5678 after loading
is completed. If pointer tracking was disabled for the type that
p and q point to, then
because the data would be written out twice (see [1]) then
p and q will end up with
different pointer values (addresses) after loading, even if they
had the same pointer value (address) at the time of
storing.
If your data has cycles in it then pointer tracking should not
be disabled. If the library detects a cycle for a type that has
pointer tracking disabled, it will throw a
"cannot_serialize_cyclical_pointer_when_tracking_of_the_pointer_type_is_disabled_exception".
To disable pointer tracking for all types, specify
WANT_POINTER_TRACKING as false for
the traits class passed to storage (it defaults to
true). See "Pointer
tracking option" above.
To disable pointer tracking for a specific type, specify
WANT_POINTER_TRACKING as true for
the traits class passed to storage (it defaults to true),
and then define a template specialization for that type, or use the
CCS_DISABLE_POINTER_TRACKING_FOR macro at global scope.
For example, if pointers to Person objects are being stored but you
don't want pointer tracking for them (because you never store the same
pointer more than once), then you can disable pointer tracking just
for Person objects by doing this:
// this must be done at global scope and not inside a namespace {...}:
CCS_DISABLE_POINTER_TRACKING_FOR(Person);or this:
namespace ccs {
namespace serialization {
template <>
class want_pointer_tracking_for<Person> { public:
enum { value = false };
};
}
}Note that if Person is a base class for a (polymorphic) class hierarchy, then the above will disable pointer tracking for all instances of Person and all instances of subclasses of Person.
If you wish to have pointer tracking globally on but default to
off for specific types, you'll need to change the source code for the
serialization library. Search for the text "class
want_pointer_tracking_for" in serialization.hpp and change the
enum from true to
false.
Given a base type BT and a type DT
that is derived from BT, then for a pointer to an
object of type BT (ie, "BT* pointer;")
which at runtime points to an object of type DT, the
library will load and save the pointer polymorphically (ie, load and
save a DT object) if these conditions are met:
the compiler is set to compile with RTTI support enabled.
Failure to do this will cause compile-time errors when the
typeid() operator is invoked by the library.
the value of the macro HAVE_RTTI is set to a
non-zero value, typically one. The default value is one.
global
polymorphic support is on, ie, the trait class passed to
storage<> specifies
WANT_POLYMORPHIC_SUPPORT as
true. This setting allows polymorphic
handling on a per-object-set granularity (recall that a single
database file can contain multiple object sets). The default is
true if the value of the macro
HAVE_RTTI is non-zero.
BT polymorphic support is on, ie,
ccs::serialization::want_polymorphic_pointer_handling_for<BT>::value
is true. This setting allows polymorphic
handling on a per-base-class granularity. The default is
true.
DT polymorphic support is on, ie,
ccs::serialization::want_polymorphic_pointer_handling_for<DT>::value
is true. This setting allows polymorphic
handling on a per-class granularity. The default is
true.
DT has been registered as a type to be
loaded/saved polymorphically when a pointer to its base class
has been encountered, ie,
"register_type<DT>();" has been called on the
storage object prior to loading/saving. If
DT has not been registered then DT
objects will be saved as BT objects if
BT is not abstract, otherwise a
cannot_serialize_abstract_type_exception
is thrown.
If any of the conditions are not met, then the pointer to a DT instance will be loaded and saved as a BT object.
Now since the global and per-type default are set to
true for all types when HAVE_RTTI
is non-zero, then by default, all types are treated as polymorphic
types.
Here is an example on how to serialize polymorphic pointers:
class DrawingContext;
class Shape {
public:
virtual ~Shape() {}
virtual void draw(DrawingContext& dc) = 0;
};
class Line : public Shape {
public:
virtual void draw(DrawingContext& dc) {
...
}
private:
friend class ccs::serialization::access;
template <typename S>
void serialize(S& s) {
s & left_ & top_ & width_ & height_;
}
float left_, top_, width_, height_;
};
class Rectangle : public Shape {
public:
virtual void draw(DrawingContext& dc) {
...
}
private:
friend class ccs::serialization::access;
template <typename S>
void serialize(S& s) {
s & left_ & top_ & width_ & height_;
}
float left_, top_, width_, height_;
};
class Circle : public Shape {
public:
virtual void draw(DrawingContext& dc) {
...
}
private:
friend class ccs::serialization::access;
template <typename S>
void serialize(S& s) {
s & x_ & y_ & radius_;
}
float x_, y_, radius_;
};
void
SaveShapes(const std::string& path, const std::vector<Shape*>& shapes)
{
ccs::serialization::storage<> db(path);
db.register_type<Line>();
db.register_type<Rectangle>();
db.register_type<Circle>();
// alternatively:
//db.register_type< ccs::tmp::tuple<Line, Rectangle, Circle> >();
// Actual types will be used on saving; loading requires the same
// registration calls.
db << shapes;
}To disable polymorphic support for all types, specify
WANT_POLYMORPHIC_SUPPORT as
false for the traits
class passed to storage (it defaults to
true). See "Polymorphic
pointer option" above.
To disable polymorphic support for a type BT and all of its descendent types:
namespace ccs {
namespace serialization {
template <>
class want_polymorphic_pointer_handling_for <BT> { public:
// BT* pointers will be loaded/saved as BT objects no matter what the actual type is:
enum { value = false };
};
}
}To disable polymorphic support for a descendent type DT:
namespace ccs {
namespace serialization {
template <>
class want_polymorphic_pointer_handling_for <DT> { public:
// BT* pointers that point to DT objects will be loaded/saved as BT objects:
enum { value = false };
};
}
}