-
Notifications
You must be signed in to change notification settings - Fork 124
Custom (De )Serialization Functions
For all standard layout, non-polymorphic aggregate types you do not have to write a custom function. However, when you have 3rd-party structs, structs with constructors or RAII structs that manage memory, etc. you need to override the serialize and deserialize functions.
By default, every value gets copied raw byte-by-byte. For each type you would like serialize with a custom function, you need to override the following function for your type.
This function will be called with
- The serialization context (described below). It provides functions to write to the buffer and to translate pointers to offsets.
- A pointer to the original value (i.e. not the serialized!) of your struct.
- The offset value where the value of
YourType
has been copied to. You can use this information to adjust certain members ofYourType
. For example:ctx.write(pos + offsetof(cista::string, h_.ptr_), start)
. This overrides the pointer contained incista::string
with the offset, the bytes have been copied to by callingstart = c.write(orig->data(), orig->size(), 1)
.
template <typename Ctx>;
void serialize(Ctx&, YourType const*, cista::offset_t const);
The Ctx
parameter is templated to support different
serialization targets (e.g. file and buffer).
Ctx
provides the following members:
struct serialization_context {
struct pending_offset {
void* origin_ptr_; // pointer to the original
offset_t pos_; // offset where it is serialized
};
/**
* Writes the values at [ptr, ptr + size[ to
* the end of the serialization target buffer.
* Adjusts for alignment if needed and returns
* the new (aligned) offset the value was written to.
*
* Appends to the buffer (resize).
*
* \param ptr points to the data to write
* \param size number of bytes to write
* \param alignment the alignment to consider
* \return the alignment adjusted offset
*/
offset_t write(void const* ptr, offset_t const size,
offset_t alignment = 0);
/**
* Overrides the value at `pos` with value `val`.
*
* Note: does not append to the buffer.
* The position `pos` needs to exist already
* and provide enough space to write `val`.
*
* \param pos the position to write to
* \param val the value to copy to position `pos`
*/
template <typename T>;
void write(offset_t const pos, T const& val);
/**
* Lookup table from original pointer
* to the offset the data was written to.
*/
std::map<void*, offset_t>; offsets_;
/**
* Pending pointers that could not yet get
* resolved (i.e. the value they point to has not
* yet been written yet but will be later).
*/
std::vector<pending_offset>; pending_;
};
To enable a custom deserialization, you need to create a specialized function for your type with the following signature:
void deserialize(cista::deserialization_context const&, YourType*);
void unchecked_deserialize(cista::deserialization_context const&, YourType*);
With this function you should:
- for the non-"unchecked" version: check that
everything fits into the buffer and that
pointers point to memory addresses
that are located inside the buffer
using
deserialization_context::check()
- convert offsets back to pointers using the
deserialization_context::deserialize()
member function.
Both functions (deserialize()
and check()
)
are provided by the deserialization_context
:
struct deserialization_context {
/**
* Converts a stored offset back to the original pointer
* by adding the base address.
*
* \param ptr offset (given as a pointer)
* \return offset converted to pointer
*/
template <typename T, typename Ptr>;
T deserialize(Ptr* ptr) const;
/**
* Checks whether the pointer points to
* a valid memory address within the buffer
* where at least `size` bytes are available.
*
* \param el the memory address to check
* \param size the size to check for
* \throws if there are bytes outside the buffer
*/
template <typename T>;
void check(T* el, size_t size) const;
};