C++ – Patterns for Handling Changing Property Sets in C++

cdesigndesign-patterns

I have a bunch "Property Sets" (which are simple structs containing POD members). I'd like to modify these property sets (eg: add a new member) at run time so that the definition of the property sets can be externalized and the code itself can be re-used with multiple versions/types of property sets with minimal/no changes.

For example, a property set could look like this:

struct PropSetA
{
    bool activeFlag;
    int processingCount;
    /* snip few other such fields*/
};

But instead of setting its definition in stone at compile time, I'd like to create it dynamically at run time. Something like:

class PropSet propSetA;
propSetA("activeFlag",true);  //overloading the function call operator
propSetA("processingCount",0); 

And the code dependent on the property sets (possibly in some other library) will use the data like so:

 bool actvFlag = propSet["activeFlag"];
 if(actvFlag  == true)
 {
   //Do Stuff
 }

The current implementation behind all of this is as follows:

class PropValue
{
 public:
    // Variant like class for holding multiple data-types
    // overloaded Conversion operator. Eg:
    operator bool()
    {
      return (baseType == BOOLEAN) ? this->ToBoolean() : false;
    }
    // And a method to create PropValues various base datatypes
    static FromBool(bool baseValue);
};

class PropSet
{

 public:
  // overloaded[] operator for adding properties
  void operator()(std::string propName, bool propVal)
  {
    propMap.insert(std::make_pair(propName, PropVal::FromBool(propVal)));
  }

 protected:
   // the property map
   std::map<std::string, PropValue> propMap;
};

This problem at hand is similar to this question on SO and the current approach (described above) is based on this answer. But as noted over at SO this is more of a hack than a proper solution. The fundamental issues that I have with this approach are as follows:

  • Extending this for supporting new types will require significant code change. At the bare minimum overloaded operators need to be extended to support the new type.
  • Supporting complex properties (eg: struct containing struct) is tricky.
  • Supporting a reference mechanism (needed for an optimization of not duplicating identical property sets) is tricky. This also applies to supporting pointers and multi-dimensional arrays in general.

Are there any known patterns for dealing with this scenario? Essentially, I'm looking for the equivalent of the visitor pattern, but for extending class properties rather than methods.

Edit: Modified problem statement for clarity and added some more code from current implementation.

Best Answer

Well, AFAIU you want some kind of reflection, but to retain static type checking the reflection data must be available for template metaprograms. There are (or at least were) several efforts to implement such a library: Mirror C++ reflection utilities, Boost libraries for reflective programming (seem to be outdated). Take a look at them at least as source of inspiration. Also take a look at Boost.Fusion, it is a library for working with heterogeneous collections of data (tuples), which in turn can represent structs.

The simplest static reflection can achieved using a separate code generator which produces code like:

struct Foo {
  int a;
  double b;
};

// Types to identify struct members
namespace name_tags { struct a; struct b; }

// Reflection metadata for struct Foo
template<>
struct DataMembers<Foo> {
  typedef boost::fusion::result_of::make_map<
    name_tags::a,
    name_tags::b,
    Member<Foo, int>,
    Member<Foo, double>
  >::type type;

  type value;
};

DataMembers<Foo>::type
DataMembers<Foo>::value = 
  boost::fusion::result_of::make_map<
    name_tags::a,
    name_tags::b>(Member<Foo, int>(&Foo::a, "a"), 
                  Member<Foo, double>(&Foo::b, "b"));

Where Member class template is like:

template<typename S, typename M>
struct Member
{
  M S::* ptr;
  std::string name;

  Member(M S::* ptr, const std::string& name) : ptr(ptr), name(name) { }
};

Then you can use Boost.Fusion to apply a functor to all members of the class. You can use additional metafunctions to mark some members, e.g. as properties to process.

Iterating over members of Foo can be done as

boost::fusion::for_each(DataMembers<Foo>::value, ProcessMember());

struct ProcessMember
{
  void operator() (const boost::fusion::pair<name_tags::a, Member<Foo, int>>& mem) const
   {
     // ...
   }

  void operator() (const boost::fusion::pair<name_tags::b, Member<Foo, double>>& mem) const
   {
     // ...
   }

   // You can also provide a default case
   template<typename T>
   void operator() (const T&) const { }
};

Tag types name_tags::a and name_tags::b appear in the signatures of the operator() and allow to provide separate processing function for each member of Foo. If you add a member to Foo and update the reflection data (this should be automated) and forget to update processing functors application of those functions will fail to compile.

Related Topic