Generic Properties for Object Data or Program Configuration.

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Josie
Posts: 44
Joined: Sat Jul 25, 2009 4:08 am
Location: Griffin, Georgia, US
Contact:

Generic Properties for Object Data or Program Configuration.

Post by Josie »

Once again, I posted a useful snippet on my blog and I thought you guys might benefit from it. It has a dependency on the boost C++ libraries ( which I highly recommend ).

---------------------------------------------------------------------------
Copied from Simple, (Almost) Elegant Generic Properties.
---------------------------------------------------------------------------

Extending on my previous post about Hash Keys, I've decided to share another recent tidbit of code. I've been playing around with a proof of concept implementation of game entity aggregation using components ( more information about the idea here ) and I found myself in need of a way to store a generic entity's properties ( such as position, size, mass, etc. ). Storing the properties as member variables in the entity class defeats the whole purpose of components, so that was out the window. I also considered storing them in the individual components, but that would put an seemly unneeded dependence on the components having to know about each other.

What I came up with is somewhat a concept borrowed from Python: A property map, or dictionary. In python, an object's properties are stored in a dictionary, and they are totally dynamic. While I cannot make C++ anywhere near as elegant as python, I did come up with a neat solution: Give the Entity class a Property Map.

So the first thing we need is a fast way to store properties. std::map was a consideration, but it's slow! Luckily, boost ( and tr1 ) has an awesome class called unordered_map. unordered_map is like map, but it's unordered and uses hashes. So now we can use an unordered_map as the container and std::strings as the key, but what about the value? Well, we can't just stick to one type for that, so our option is either some sort of union, boost::variant, or boost::any. A union is a pretty C way to tackle a very C++ problem, and doesn't really do us a lot of good in the end. Boost::variant is a decent choice, but requires us to change our template declaration every time we want to use a new class. Boost::any is pretty much perfect for this job, it has elegant assignment, and a nice, C++ style way of casting. So here's what our container looks like:

Code: Select all

typedef boost::unordered_map< std::string, boost::any > property_map;
Not too bad, but we hardly want to access this directly! Let's encapsulate this in a nice class that we can extend.

Code: Select all

#include <boost/unordered_map.hpp>
#include <boost/any.hpp>

class PropertyContainer
{

public:

    PropertyContainer()
        : properties()
    {}

    ~PropertyContainer()
    {}

    //! Set property value.
    void set( const std::string& _key, const boost::any& _value )
    {
        properties[_key] = _value;
    }

    //! Get boost::any from property map.
    const boost::any& get( const std::string& _key )
    {
        return properties[ _key ];
    }

    //! Get Property Value
    /*!
        Templated get that automatically casts to the given type.
        You can also specify a default value in the case that the
        property doesn't exist.
    */
    template <class T>
    const T get( const std::string& _key, const T& _def = T() )
    {
        boost::any anyres = get( _key );
        try
        {
            return boost::any_cast<T>( anyres );
        }
        catch( boost::bad_any_cast )
        {
            properties[ _key ] = _def;
            return _def;
        }
    }

    //! Element Access Operator
    boost::any& operator[]( const std::string& _key )
    {
        return properties[_key];
    }

    typedef boost::unordered_map< std::string, boost::any> property_map;

    property_map& getPropertyMap(){ return properties; }

private:
    
    property_map properties;
};
The most useful thing we get out of this is the fact that get accepts a default value and is templated. We still get the element access operator in the case where we don't want to use get and set. Let take a look at some use cases

Code: Select all

PropertyContainer properties;

properties["name"] = std::string("Test Container.");
properties.set( "x", 4.0f );

std::cout<<"Name: "<<properties.get<std::string>("name")<<std::endl;
std::cout<<"X: "<<boost::any_cast<float>(properties["x"])<<std::endl;
std::cout<<"Y: "<<properties.get("y",0.0f)<<std::endl;
Simple enough, right? We a little bravery you can easily serialize and de-serialize this class, providing a neat way to store object data or even program configuration data, I'll leave that as an exercise for the reader!

Also, you'll notice that it's easy to mix up "X" and "x" ( a problem that I run into a lot, as mentioned in my last post ). We fixed this problem with our hash key class. You can either use HashKey as the key for the property_map, or you can take advantage of the third and fourth parameters for the unordered_map template: The hash function and the equality function, respectively. With one more function to our HashKey class, we can use case-insensitive strings with our properties:

Code: Select all

typedef boost::unordered_map< std::string, boost::any, HashKey::hash_function, HashKey::equal_to> property_map;
The implementation of equal_to is once again left as an exercise for the reader.
Post Reply