Using a template variable within a map

Started by
3 comments, last by mattm 19 years, 1 month ago
Hi, I want to use a template class and plass the template type (which will be a class) into a map. To help explaing this, this is a section from an include file:

	template<class Type>
	RegisterEntityClass(int iEntityName, class Type);

private:
	static cFactory* mpSingleton;
	std::map<int, Type> InstanceMap; 

Obviously this will not work (or compile) as the map has no knowledge of the template type. I do not want to make the entire class a template. I had thought of dynamically creating the map at runtime within the templated function but am unsure the best way to then store it within the class; for example do i use a void* (urg) or something else? Any help would be greatly appreciated. Thanks, Matt
Advertisement
Are you saying you want a singleton Factory class that provides requests for absolutely any type of resource? Assuming this is the case, then here are some suggestions:

Use polymorphism. Create an abstract interface class that all resources implement. Then use a std::map<int, ResourceBase*> (or even use a smart pointer instead of a plain pointer).

Use std::map<int, boost::any>.

Otherwise you'll need to template the class, or use a pre-built factory class, such as that found in the Loki library.

More detail would also help - lack of context means recommending all options - there might be a much better way of doing what you want.

Jim.
Thanks for the help. I have also been thinking about this further and am not sure how to acomplish what i want, even overcoming this issue.

Effectivley i want the ability to call a factory class, passing in an identifier, and it returning a pointer to an instance of the required class. This is for two reasons:

1) The instancing class will not need to know the class type.
2) If i change the handling of the objects (e.g. move to handles from pointers) the creation will all be centralised and easier to implelment.

Currently all objects within my code are inherited from an abstract base class for easy use with my scene graph and quad tree (or whatever spatial graph i implement). However, there are obviously derived classes (currrently Object (it has a bound) and entity (it can be selected and manipulated)) Obvioulsy, if my Factory returns the absolute base class i loose the functionality in my higher classes, which is undesirable.

I am currently considering returning an entity from my Object factory, and if i find i need it create more than one object factory of a different base class. However, i will then still need to create the camera and terrain classes seperately as even though these are inherited from the base class they need to implement their own functions.

The ideal situation i was looking for was the ability to register an instance name (e.g. ENTITY) with a partular class and store this in a map. A call to create would then look up the entity class and return the desired pointer. However, although i managed to implement this with template classes and void* pointers (urgh again) I realised i could still not return the required class from the create method and would have to return a base class. Although i could then cast this base pointer back to the deisred one it would mean the create call would have to have knowledge of the actual class, loosing one of the key benefits for me. I really am a little confused as to where to go from here and although i can avoid the issue for now i would using the good old new i would like to get this clear in my mind.

Once again thanks,

Matt
Here's my solution to a problem, which may not be a million miles away from what you want. It's basically the Loki object factory implementation. Here's my (stripped-down) code:

First of all, the entitymanager:
#include <map>#include <vector>#include <string>#include <boost/shared_ptr.hpp>#include <boost/any.hpp>#include <singleton.h>class Entity;class SingletonEntityFactory{public:// I'll comment on this horrendous approach in a mo...	typedef boost::shared_ptr<Entity> (*CreateNewEntityCallback)(const std::vector<boost::any>&);public:// This function allows new entity classes to register themselves with the factory	bool RegisterEntityType(std::string whichEntity, CreateNewEntityCallback newEntityFn);// And they can then be requested using their calling string	boost::shared_ptr<Entity> RequestEntity(const std::string& whichEntity, const std::vector<boost::any>& details);private:	typedef std::map<std::string, CreateNewEntityCallback> theAvailableEntitiesTypedef_;	theAvailableEntitiesTypedef_ theAvailableEntities_;};// Turn this class into a singleton, with a bit of Loki magic!typedef Loki::SingletonHolder<SingletonEntityFactory> EntityFactory;// Now some implementationbool SingletonEntityFactory::RegisterEntityType(std::string whichEntity, CreateNewEntityCallback newEntityFn){	return theAvailableEntities_.insert(std::make_pair(whichEntity, newEntityFn)).second;}// No error-trapping in here currently - hmm, should really get around to thatboost::shared_ptr<Entity> SingletonEntityFactory::RequestEntity(const std::string& whichEntity, const std::vector<boost::any>& details){	theAvailableEntitiesTypedef_::iterator it = theAvailableEntities_.find(whichEntity);	boost::shared_ptr<Entity> thisEntity = (it->second)(details);	return thisEntity;}


Now the entity interface:
#pragma once#include <boost/shared_ptr.hpp>class Entity{public:	Entity(void);	virtual ~Entity(void);	virtual void UpdateAI() = 0;	virtual void Render() = 0;	virtual void UpdateLocation() = 0;};


And now the relevant parts of a specific entity - header excluded, as it's pretty obvious. This is the clever part...
#include stuff// Create an an anonymous namespace to mangle some namesnamespace{// Define a creation function	boost::shared_ptr<Entity> thisCallback(const std::vector<boost::any>& details)	{		boost::shared_ptr<Entity> thisEntity(new EntityPlayer(details));		return thisEntity;	};// This is the clever part - explanation below	const bool registerEntityPlayer = EntityFactory::Instance().RegisterEntityType("Player", thisCallback);}EntityPlayer::EntityPlayer(const std::vector<boost::any>& details){// Use the vector<boost::any> to set up some members - again, comments to come on this}


And finally, how to call:
// Please ignore the magic numbers...// A callback function for the hotspot, used belowvoid SensorPathFindCallback(){	StateManager::Instance().PushState("PathFind");}void StateIntroduction::OnEntry{// Create a store for the ctor parameters	std::vector<boost::any> thisVec;// Enter the ctor parameters - this is for a Hotspot - just a clickable text region	int leng = GraphicsManager::Instance().GetStringWidth("Sensor-based Path-finding");	thisVec.push_back(Vector2(400 - leng / 2, 100));	thisVec.push_back(std::string("Sensor-based Path-finding"));	thisVec.push_back(&SensorPathFindCallback);	thisVec.push_back(Vector2(400 - leng / 2, 100));	thisVec.push_back(Vector2(400 + leng / 2, 140));// Finally, request a pointer to the new entity// Strictly speaking, my program doesn't work like this, but gives you the idea of usage	boost::shared_ptr<Entity> thisEntity = EntityFactory::Instance().RequestEntity("Hotspot", thisVec);}


OK - here's how it works.

Each new entity is derived from the Entity base class; in it's cpp file it has that little anonymous namespace piece of code. In the example above, a calling function is defined (which matches in signature to the requirements of entity factory - check the typedefs). This function, and associated calling string, is passed to the factory at program start-up.

This happens because the variable registerEntityPlayer is a global variable (albeit one in it's own little namespace), and hence evaluated at start-up time. In order to calculate this variable, EntityFactory::Instance must be called - creating a static singleton entityFactory - so relying upon the properties of static member variables. All this occurs as the program starts, so as your code starts to execute everything is set up ready to play with - you can start requesting entities from the factory using their calling strings.

Unfortunately, as far as I'm aware it's impossible to have different ctor parameters in your factory-function (ie in the CreateNewEntityCallback typedef) - if anyone can show me how to get around this I'm all ears! I solved this little issue by using std::vector<boost::any>, which to my mind is analogous to C's ... parameter - so, it's goodbye to type-safety. Given that you already have to remember the calling string for each specific entity, I have assumed that remembering the order and type of parameters isn't too much of a pain - if you're particularly worried about it, check on entry to the entity ctor during debug builds. I also assume that there is a small performance hit (if you're worried about that sort of thing) - although this only occurs at the time of object creation.

Note finally that this approach limits compile time dependencies - making adding new entities a breeze. Each entity then handles it's own operations, by using it's public interface (through the Entity base class) as it's only calling parameter external to the object itself.

Quote:
However, although i managed to implement this with template classes and void* pointers (urgh again) I realised i could still not return the required class from the create method and would have to return a base class. Although i could then cast this base pointer back to the deisred one it would mean the create call would have to have knowledge of the actual class, loosing one of the key benefits for me.


I think you can do this using a Clone method, but off the top of my head I can't remember how to do it. Will have a look around.


So - having now exposed the inner-secrets of my programming style, anyone passing can feel free to rip it to pieces. It would be only polite[smile].

Jim.

Edit : on the clone method : you might want to have a look at snk_kids last example in this thread. Although I think it will increase compile time dependencies to integrate this method (because you'll have to increase the number of #includes). Horses for courses though - depends on what's important to you.

[Edited by - JimPrice on March 8, 2005 6:06:06 PM]
Fantastic. I will have to look through that tomorrow to make sure i understand it (too late/early now, not a chance of getting my head around it!) but thanks for taking the time to provide it.

Matt

This topic is closed to new replies.

Advertisement