Extendible
Use Case:
A data class that inherits from Extendible, uses a CRTP (Curiously Recurring Template Pattern) so that its static Schema
instance is unique among other types. Thus all instances of the type implicitly know memory layout of the fields.
class ExtendibleExample : public ext::Extendible<ExtendibleExample> {
int real_member = 0;
}
The documentation and code refers to the Derived type as the class that is inheriting from an Extendible
.
During system init, code and plugins add fields to the ’s schema. This will update the Memory Layout of the schema, and the memory offsets of all fields. The schema does not know the field’s type, but it stores the byte size and creates std::functions of the type’s constructor, destructor, and serializer. And to avoid corruption, the code asserts that no instances are in use when adding fields.
When an derived class is instantiated, will allocate a block of memory for the derived class and all added fields. The only memory overhead per instance is an uint16 used as a offset to the start of the extendible block. Then the constructor of the class is called, followed by the constructors of each extendible field.
ExtendibleExample* alloc_example() {
return ext::create<ExtendibleExample>();
}
One block of memory is allocated per Extendible
, which include all member variables and extended fields. Within the block, memory is arranged in the following order:
Derived members (+padding align next field)
Fields (largest to smallest)
Packed Bits
When using inheritance, all base cases arranged from most super to most derived, then all blocks are arranged from most super to most derived. If the fields are aligned, padding will be inserted where needed.
template<typename Derived_t, typename Field_t> FieldId
is a templated Field_t
reference. One benefit is that all type casting is internal to the Extendible
, which simplifies the code using it. Also this provides compile errors for common misuses and type mismatches.
Unfortunately it is non-trivial handle multiple super types in the same inheritance tree.
template<> create()
handles allocation and initialization of the entire Derived class, but it is dependent on each class definingusing super_type = *some_super_class*;
so that it recurse through the classes.
struct A : public Extendible<A> {
uint16_t a = {1};
};
struct B : public A {
using super_type = A;
uint16_t b = {2};
};
struct C : public B, public Extendible<C> {
using super_type = B;
uint16_t c = {3};
};
ext::FieldId<A, atomic<uint16_t>> ext_a_1;
ext::FieldId<C, uint16_t> ext_c_1;
C &x = *(ext::create<C>());
ext::viewFormat(x);
prints a diagram of the position and size of bytes used within the allocated memory.
See src/tscore/unit_tests/test_Extendible.cc for more examples.
Namespace ext
template<typename Derived_t
, typename Field_t
>
class FieldId
The handle used to access a field. These are templated to prevent human error, and branching logic.
Template Parameters
Derived_t – The class that you want to extend at runtime.
Field_t – The type of the field.
typedef const void *ExtFieldContext
The handle used to access a field through C API. Human error not allowed by convention.
Allocates block of memory, uses and Schema
to access slices of memory.
static
schema
one schema instance per
Extendible
to define contained
template<typename Derived_t
>
Extendible *create
()
To be used in place of new Derived_t(). Allocate a block of memory. Construct the base data. Recursively construct and initialize Derived_t::super_type and its classes.
Template Parameters
Derived_t – The Derived class to allocate.
template<typename Derived_t
, typename Field_t
>
bool fieldAdd
(FieldId<, Field_t> &field_id, std::string const &field_name)
Declare a new for Derived_t.
Template Parameters
Derived_t – The class that uses this field.
Field_t – The type of the field.
template<typename Derived_t
, typename Field_t
>
bool fieldFind
(FieldId<, Field_t> &field_id, std::string const &field_name)
Find an existing for Derived_t.
Template Parameters
Derived_t – The class that uses this field.
Field_t – The type of the field.
template<typename T
, typename Derived_t
, typename Field_t
>
auto const get
(T const&, <Derived_t, >)
Returns T const& value from the field stored in the Extendible
allocation.
Template Parameters
T – The class passed in.
Derived_t – The class that uses this field.
Field_t – The type of the field.
template<typename T
, typename Derived_t
, typename Field_t
>
auto set
(&, FieldId<, Field_t>)
Returns T & value from the field stored in allocation.
Template Parameters
Derived_t – The class that uses this field.
Field_t – The type of the field.
template<typename Derived_t
>
size_t sizeOf
()
Recurse through super classes and sum memory needed for allocation.
Depends on usage of super_type in each class.
Template Parameters
Derived_t – The class to measure.
template<typename Derived_t
>
void viewFormat
()
Recurse through super classes and prints chart of bytes used within the allocation.
Depends on usage of super_type in each class.
Template Parameters
Derived_t – The class to analyze.
template<typename T
>
std::string toString
( const &t)
Convert all extendible fields to std::strings (in a YAML-like format) using the serializeField()
template<typename Field_t
>
void serializeField
(std::ostream &os, const &f)
Converts a single field into a std::string (in a YAML-like format).
Specialize this template or overload the operator<< for your field to convert properly.
This is very useful when debugging.
Template Parameters
Derived_t – The field data type.
Namespace ext::details
class FieldDesc
Defines a span of memory within the allocation, and holds the constructor, destructor and serializer as std::functions.
Effectively the type-erased version of template<typename Derived_t, typename Field_t> FieldId
.
class
Manages a memory layout through a map of .