For a slightly more OO approach, I can see one possible solution to the problem of using a generic interface into a resource system. This solution provides the following client interface.
/** * Client Code example: */ int lives = ResourceAdapter<int>::get( aRepositoryManager, "player1/lives" ); MeshData shipData = ResourceAdapter<MeshData>::get(aRepositoryManager, "player1/lives" );
However if this is to be built on top of a true generic resource system interface, say:
class Respository{ BaseProperty* getProperty(const std::string& p_URLPath);}
Then at some point the code is going to need to cast BaseProperty when extracting the int or MeshData (from the client code example). At the moment the best I've been able to do is:
- limit the number of places where client code specifies types, two places, if you count the storage type 'int i' and the template parameter.
- get the runtime to perform type checking and throw an exception when an incorrect type is retrieved. (ensuring the client code never receives an incorrectly cast type)
Anyway down to the code, it's probably best to look at this in a few small chunks. This idea relies on using URLs and Properties. URLs specify values to retrieve. Properties are used to wrap the value you want to return.
The important bit here is the URL contains both the identity of the resource and the type it should be. The BasePropery and Property<T> are wrappers to hold the value that's being returned and its type. Later we'll use these together to perform type checking.
URL and Property classes:
/** * The URL is key used to identify resources or properties. Think of URL as a configurable * abstraction of a filename. * i.e. Filenames identify files (resource/ property) within a file system (repository) * Configurability is provided by allowing the Resource Package to perform mappings * for URLs to resources. * * The URL class contains the following members: * @li A path attribute navigates though a series of (possible nested) repository objects * and ultimately referes to a property. * @li A type attribute is optional, providing the ability to ensure the URL refers to * a specific property type. If the property and type do not match an error will * be generated. */class URL{ public: /// Path Iterator, an iterator type for read only path traversal typedef std::list<std::string>::const_iterator path_iterator; /// List of path sections typedef std::list<std::string> stringlist; /// Attribute map typedef std::map<std::string,std::string> attribute_map; /** * Construct a URL object, using the supplied path and optional type. * @param p_Path A path of URL steps delimited using forward slashes '/'. * @param p_Type A optional type identifier. */ URL(const std::string& p_Path, type_info const* p_Type); ...}/** * The BaseProperty interface represents a resource element, this contains sometype of value. * This contained value can be accessed by finding the actual resource type (getResourceType) * casting to it and using specific accessors */class BaseProperty{ public: /** * Virtual destructor, allow correct destruction through base class pointers */ virtual ~BaseProperty(){}; /** * Accessor method, get the type_info object for the contained value */ virtual type_info const* getResourceType() const = 0; /** * Accessor method, get the URL that refers to this Property. */ virtual const URL& getURL() const = 0;};/** * Property Implementation class, this class provides the ability to carry a templated * value around. */template<typename T>class Property : public BaseProperty{ public: /** * Construct a Property<T> using the supplied URL and value * @param p_Key URL * @param p_Value value */ Property (const URL& p_Key, T p_Value) :m_Key(p_Key), m_Value(p_Value), {} /** * Accessor for the contained value */ T get() { return m_Value; } virtual const URL& getURL() const { return m_Key; } virtual type_info const* getResourceType() const { return m_Key.getType(); } const Property<T>& operator= (const Property<T>& rhs) { if ( this != &rhs ) { m_Key = rhs.m_Key; m_Value = rhs.m_Value; } return *this; } private: /** * The URL associated with this property. */ URL m_Key; /** * The value this property currently contains. */ T m_Value;};
So back to our generic resource system, as per everyone else's suggestions.
class Respository{ BaseProperty* getProperty(const std::string& p_URLPath);};
We can then retrieve from the generic resource system using the following adapter classes:
Note: all these really do is handle the typing and checking of the request and return type.
/** * The ResourceManager class wraps the Repository providing a more userfriendly interface. * It does this by delegating to the Repository, but handling all type (casting) related issues. */class ResourceManager{ public: enum Result { INTERNAL_ERROR, INCORRECT_TYPE, NOT_FOUND, FOUND }; /** * Extract a Property from this ResourceManager. * @param p_URLPath a URL key for the resource property. * @param p_OutputProperty pointer to store the output Property. This pointer must * be valid (may not be NULL) * @return true, indicating the property is successfully retrieved. */ template<typename T> Result getProperty(const std::string& p_URLPath, Property<T>* p_OutputProperty) { URL identifier (p_URLPath, &typeid(T)); BaseProperty* matchingProperty = m_RootResource->getProperty (identifier); if (matchingProperty != NULL) { if ( matchingProperty->getResourceType() == &typeid(T) ) { Property<T>* typedProperty = dynamic_cast<Property<T>*>(matchingProperty); if (typedProperty != NULL) { *p_OutputProperty = *typedProperty; return FOUND; } // should never happen, if typeids match - you'd hope a dynamic_cast will work? return INTERNAL_ERROR; } return INCORRECT_TYPE; } return NOT_FOUND; } private: Respository m_RootResource;}/** * An adapter class that calls a templated member function based on desired return type. */template <typename T>class ResourceAdapter{ public: /** * Extract a resource value from the ResourceManager. Or throw an exception * if the resource does not exist. * @param p_Manager the ResourceManager to extract from. * @param p_Identifier a 'URL' type key for the resource value. * @return resource value. */ static T get (ResourceManager& p_Manager, const std::string& p_Identifier) { Property<T> typedProperty; ResourceManager::Result result = p_Manager.getProperty(p_Identifier, &typedProperty); if ( result == ResourceManager::FOUND ) { return typedProperty.get(); } else { // ToDo: add more meaning full errors based on ResourceManager::Result throw URLNotFoundException( URL(p_Identifier, &typeid(T)) ); } }}
So the code still contains type casting, but it's compiler generated whenever a resource is retrieved.
If you've made it this far, was it worth the effort of going to all this just to avoid manually having to write a few casts?
I guess I enjoyed trying to write this, and I liked the notation in the client code - but on balance I'm not so sure this is the right design for generic resource handling.
However hopefully this will get you thinking :)
Cheers,
Tom