Motivation
Not very long ago, I was tasked with interfacing a text-driven rule processor with our product. In order to get it to work, I needed to provide data from our product to the processor. Our product has several classes which contain an obscene amount of data. One class has nearly 250 fields. The rule processor would ask for data that it needs and we would find the class, find the ‘get
’ property method on the class and marshall the data over. Of course, the problem is determining which data out of the hundreds of field across several objects and that would require a lookup table, which I was not keen on writing.
So what I wrote was a wrapper class for our data classes (which do a lot more than storing data) to provide a lookup mechanism for the getters and setters of the fields and to provide an array interface on those methods.
This method uses object members and templatizes them so that the methods themselves can be abstract away.
Besides, I was looking for a good example to use templates with class non-static members.
The Fluff
Class Variable
is just a generic box object, similar to the VARIANT structure used by Microsoft and COM back in the old days. I just scaled it way down. The reason for it is to have a standard input and output to the Wrapper
class. I could have done the code without it but this makes everything easier.
Class PropertyException
is just an exception class. Not much to see here.
Struct DeleteMappedObject
and PrintObject
are just some helpers for cleaning up and printing.
Classes Object1
and Object2
are the target classes that we will use to test out the Wrapper. They each have three properties. They inherit from an interface template class Target<T>
which provides a method for transferring the getters and setters to the Wrapper
.
The Stuff
setMethod
and getMethod
are template structures which will hold the class method for setting or getting a value from the object. I have defined the basic method we will need, a union
struct because we only need one of them and a variable_type
parameter to know what kind of value we expect from the class method. Basically:
template <class T>
struct setMethod
{
typedef void (T::*setDouble)(double);
typedef void (T::*setBoolean)(bool);
typedef void (T::*setInteger)(int);
typedef void (T::*setCharacter)(char);
typedef void (T::*setString)(const std::string&);
union MethodType
{
void* pointer;
setDouble sD;
setBoolean sB;
setCharacter sC;
setInteger sI;
setString sS;
} m_type;
Variable::variable_type m_vt;
…
}
Additionally, there are several setMethod()
calls to set the method in m_type
and a method called set()
which passes the variable to the method on the object:
void set(Variable & _value, T * _ptr)
{
if (_ptr)
{
switch (m_vt)
{
case Variable::v_float:
(_ptr->*(m_type.sD))((double)_value);
break;
case Variable::v_boolean:
(_ptr->*(m_type.sB))((bool)_value);
break;
case Variable::v_character:
(_ptr->*(m_type.sC))((char)_value);
break;
case Variable::v_integer:
(_ptr->*(m_type.sI))((int)_value);
break;
case Variable::v_string:
(_ptr->*(m_type.sS))((const std::string&)_value);
break;
}
}
}
For example:
(_ptr->*(m_type.sD))((double)_value)
ptr
is the object that we are wrapping. m_type
is the union
struct for the method types. sD
is the setDouble()
method type. *(m_type.sD)
is the dereferenced method on the object. _value
is the value that we are passing into sD
as defined by its object.
The template struct getMethod
is similar to setMethod
.
The template class Target
is a simple interface class inherited by the Object
class to provide a method which passes the get
and set
methods of the class to the Wrapper
class. There may be a better way of handling this than inheriting a class interface but for this article, it is how I built it to make it simple.
Class Property
is an interface class which defines get
and set
operator on the class.
class Property
{
public:
Property() {}
virtual ~Property() {}
virtual operator Variable() = 0;
virtual Variable & operator = (Variable &) = 0;
};
Template class TemplateProperty
inherits from class Property
. It maintains the get
and set
methods as well as the name used for the get
and set
methods and it implements the get
and set
operators from Property
interface.
The Wrapper
class is where everything is tied together. It maintains a list of names to reference the methods. It maintains a map of Property
objects. It has a method for adding the get
and set
methods to the map of Property
objects. It can tell you if a Property
object exists for a specific name. It has an array operator to allow you to access the Property
objects by name so that you can get or set values for the method in the Property
object. It also maintains the object that it wraps.
class Wrapper
{
public:
typedef std::list<std::string> NameList;
typedef std::map<std::string, Property*> PropertyMap;
public:
...
Property& operator[](const std::string& _name)
{
Property* p = nullptr;
if (exists(_name))
p = map[_name];
else
p = map["NULL"];
dynamic_cast<TemplateProperty<T>*>(p)->setObject(ptr);
return *p;
}
private:
NameList list;
PropertyMap map;
T* ptr;
};
The Property
is returned but you can access its get
and set
methods in code:
double vx = (double)V(w2[X]);
I use the macro V(x) to apply a static_cast
to property to get the Variable
object that maintains.
Finally, there is the WrapperTest
where we see how to instantiate and call methods on the Wrapper
object. The expected output is in the source code.
I have tested this on CentOS 6 and Windows 10. The CMakeLists.txt is what I have used to build the binaries. I have used this in Linux and it also works with Visual Studio 2019 Community Edition.
History
- 29th October, 2019: Initial version