---------------------------------------------------------------------------
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;
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;
};
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;
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;