To help populate the data model, some template functions can help extract useful data via template parameters (see Listing 3).
A template function with parameters for the object type and variable type provides an easy way to extract the size of the variable and its offset from the base instance pointer (using a pointer to member variable), while also supporting the use of template specialization to deduce which type of data object is applicable to this field. Three important things are happening in this function to extract data for our reflection data model: pointer to member variable C++ syntax, translation of this syntax into an offset from a base object address, and the use of deduction using explicit specialization.
Pointer to member variables are a pretty infrequently used aspect of C++. It does what you might expect, but its syntax is strange if you haven't seen it before:
int32_t Object::* pointer_to_member_variable = &Object::m_Member;
// These are typically dereferenced with an instance of the object
// type (just like member function pointers):
Object object, *pointer = new Object;
int32_t value1 = object.*pointer_to_member_variable;
int32_t value2 = pointer->*pointer_to_member_variable;
// To compute the offset from a pointer to a member variable
template< class ObjectT, class DataT >
uint32_t GetFieldOffset( ObjectT DataT::* field )
// a pointer-to-member is really just an offset value
// disguised by the compiler
return (uint32_t) (uintptr_t) &( ((ObjectT*)NULL)->*field );
This function doesn't bother with allocating an instance to dereference the pointer to member variable. It substitutes a NULL pointer, deferences the pointer to member variable, and uses the address operator to yield the offset (from NULL) at which the pointed member exists. Some of this syntax may seem strange, but it's a perfect fit for maximizing what information is needed to describe a field in a single function parameter.
DeduceDataClass is a good example of template deduction using explicit template specialization. This deduction technique is a way of using the C++ template mechanism to allow for the automatic selection of some information by the template compiler based only on a template parameter. The default template function's implementation returns NULL, indicating that the deduction failed since no specialization was found to find the associated data, as below:
template< class DataT >
// unknown data!
// Then create an explicit specialization for every type that can
// be deduced:
// this specialization associates the uint32_t built in
// type with an object class that can
// process data of type uint32_t with respect to other
// persistence / cloning / mining code
return SimpleData< uint32_t >::s_Class;
In this case, a pointer is returned to the class reflection information for the type of data object to be used when dealing with the built-in type passed into the template argument. One more template will help keep the code that registers classes at startup concise, as seen in Listing 4.
Reflection can imbue an enormous amount of flexibility to your game engine, but this flexibility doesn't come without cost. However, the extra memory reflection data consumes is balanced by the time saved implementing features more rapidly. The ability to deliver changes to your users quickly, and with minimal engineering overhead, will pay dividends as your user base grows and your production time stretches across multiple titles.
Helium is an open source game engine toolkit that contains an implementation of C++ Reflection. Much of the code in this article was derived from it. It uses a BSD-style license, and is available at www.heliumproject.org. The reflection system itself is located in the Foundation/Reflect folder within the source repository.