Reflection Visitors Tutorial
In this quick tutorial we show how to perform more complex operations on opclasses and opstructs in opC++ via reflection visitors and data accessors.
While using external class/field reflection allows you access to data and fields listed in a class declaration, it does not allow you to access nested data structures - like the elements of containers for example.
In this example we'll use reflection visitors and accessors, which allow you abstracted and direct access to all reflected data. In particular, we will write a visitor to determine the size in bytes of an object's data at runtime. There are a few caveats to byte counting, but we'll get a reasonable picture of the serializable byte count using a visitor.
Consider the following class and opclasses:
In the code above, we define two opclasses: Payee and BankAccount. They each contain some data members, and BankAccount contains some STL containers of data members (one containing basic types, one containing Payee opclasses).
To implement our byte counting we'll need to use an opC++ visitor - an object with several visit function overloads that will receive data accessors. Accessors are polymorphic objects that provide you with an interface to manipulating data without needing to know the exact type you're dealing with.
We will now write a visitor that determines the total memory size of an instance of BankAccount:
We'll keep track of the number of bytes in our visitor using size_t NumBytes.
Visitors in opC++ need to inherit from opcpp::base::visitor_base and implement one or more visit functions. Each visit function needs to accept a data accessor reference - we'll first implement the member_info accessor visit function:
This function will get called on the visitor for each data member in a class - member_info is the base accessor type and this function will act as a catch-all. In this function we add the number of bytes in the type to the current count.
The type_size function will return the same value as using the sizeof() operator on the accessors data.
We also want to count the bytes within a string, including the dynamically allocated memory for the string. So here we overload the visit function for the string_info accessor type, which gives us access to any strings in our instance.
With these two visit functions overloaded, the visitor will count strings with this function and count everything else using the member_info overload.
Since we may also have opclass data members, we can overload for class_info to handle
opclasses. Here we forward this visitor to the opclass, which will then have all its
members use our visitor.
If we have any stl containers, we'll also want to count their elements. Here we overload
for container_info and iterate the contents, while visiting each element to get their sizes.
We could also overload for more specific container accessors like list, vector, etc - but here
we can just use the most derived accessor for containers.
Now lets actually use this visitor class with our opclass instance:
Using the visit_data_members function, our visitor instance will get a data accessor for each member in our class through the visit functions. By using these accessors we were able to count the bytes in that instance:
Visitors in opC++ are an extremely fast way to access whole data structures - accessors and visitors are both created on the stack and incur no dynamic allocations. The Standard opC++ Serialization uses visitors like these recursively to easily serialize complex data structures.
Project Files (VS 2005): ReflectionVisitors.zip