So, I’ve been recently in need of a simple reflection system that would allow me to pass my POD structures through a generic interface as BLOBs, without loosing the type information about the POD attributes on the way.
After a few iterations, I’ve come up with quite a simple system, similar to this:
VIEW THE CODE BELOW IN FULL-SCREEN (reflection_sample.cpp)
Code: Select all
/*
(c) 2012 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=724
*/
#include <array>
#include <vector>
#include <string>
#include <algorithm>
#include <cstdio> // printf
#include <cstdint> // uint16_t, ...
#include <cstddef> // offsetof
#include <cassert>
const uint16_t c_invalid_index = uint16_t(-1);
struct Vector4 { float x, y, z, w; };
/// Describes various type characteristics.
struct Type
{
std::string name;
uint16_t byte_size;
//...
Type(const std::string &name, uint16_t byte_size):
name(name), byte_size(byte_size)
{}
};
/// Manages a table of types, indexed by uint16_t.
class Type_table
{
public:
/// Defines a basic set of types, more can be added later using add().
Type_table()
{
add("float", sizeof(float));
add("Vector4", sizeof(Vector4));
}
/// Adds a new type and returns its index.
uint16_t add(const char *type_name, uint16_t byte_size)
{
assert(type_name && byte_size > 0);
_types.emplace_back(Type(type_name, byte_size));
return static_cast<uint16_t>(_types.size() - 1);
}
/// A direct read-only access to the internal 'Type' can't harm ;)
const Type & get(uint16_t type_index) const
{
assert(type_index < _types.size());
return _types[type_index];
}
/// Finds a type by name, returns 'c_invalid_index' if not found.
uint16_t find(const char *type_name) const
{
assert(type_name);
const auto it = std::find_if(
_types.begin(), _types.end(),
[&type_name](const Type &type) { return type.name == type_name; }
);
return
it != _types.end() ?
static_cast<uint16_t>(std::distance(_types.begin(), it)) :
c_invalid_index;
}
private:
std::vector<Type> _types;
};
/// Describes a single struct attribute.
struct Struct_attribute
{
uint16_t type_index;
uint16_t byte_offset;
Struct_attribute():
type_index(c_invalid_index), byte_offset(0)
{}
Struct_attribute(uint16_t type_index, uint16_t byte_offset):
type_index(type_index), byte_offset(byte_offset)
{}
};
/// Describes a whole struct (made of attributes) in a generic way.
struct Struct_descriptor
{
const void *data;
size_t byte_size;
const Struct_attribute *attribs_begin;
Struct_descriptor(const void *data, size_t byte_size, const Struct_attribute *attribs_begin):
data(data), byte_size(byte_size), attribs_begin(attribs_begin)
{}
};
/// A sample class with a 'Data' block that supports reflection.
class Material
{
public:
Material(const Type_table &type_table)
{
const Attribute_array data_attribs =
{
Struct_attribute(type_table.find("Vector4"), offsetof(Data, inner_color)),
Struct_attribute(type_table.find("Vector4"), offsetof(Data, outer_color)),
Struct_attribute(type_table.find("float"), offsetof(Data, point_size)),
Struct_attribute() // terminator
};
// one unnecessary copy of 'data_attribs' here is worth the initialization through {}
_data_attribs = data_attribs;
}
Struct_descriptor data_descriptor() const
{ return Struct_descriptor(&_data, sizeof(_data), _data_attribs.data()); }
const Vector4 & inner_color() { return _data.inner_color; }
void set_inner_color(const Vector4 &color) { _data.inner_color = color; }
//...
private:
/// The actual reflected structure.
struct Data
{
Vector4 inner_color;
Vector4 outer_color;
float point_size;
} _data;
typedef std::array<Struct_attribute, 4> Attribute_array;
Attribute_array _data_attribs;
};
/// A function that demonstrates how to work with a 'reflected' structure in a generic way.
void dump(const Type_table &type_table, const Struct_descriptor &desc)
{
assert(desc.data);
assert(desc.byte_size > 0);
assert(desc.attribs_begin);
printf("struct_byte_size=%d\n", desc.byte_size);
for(const Struct_attribute *attrib = desc.attribs_begin; attrib->type_index != c_invalid_index; ++attrib)
{
const Type &type = type_table.get(attrib->type_index);
printf(
"type_index=%d, byte_offset=%d, type_size='%s', type_size=%d\n",
attrib->type_index, attrib->byte_offset, type.name.c_str(), type.byte_size
);
}
}
int main()
{
const Type_table type_table;
const Material material(type_table);
dump(type_table, material.data_descriptor());
const Material material2 = material; // note that copying is almost for free, with no heap allocations!
dump(type_table, material2.data_descriptor());
return 0;
}
// output:
// struct_byte_size=36
// type_index=1, byte_offset=0, type_name='Vector4', type_size=16
// type_index=1, byte_offset=16, type_name='Vector4', type_size=16
// type_index=0, byte_offset=32, type_name='float', type_size=4
// struct_byte_size=36
// type_index=1, byte_offset=0, type_name='Vector4', type_size=16
// type_index=1, byte_offset=16, type_name='Vector4', type_size=16
// type_index=0, byte_offset=32, type_name='float', type_size=4