Generic pointer to template class (confusion)

Started by
21 comments, last by JasonBlochowiak 17 years, 9 months ago
Okay, I've been trying to wrap my head around this one for a couple days, I'm hoping someone can point me in the right direction. I've got a class used for storing game settings (like cvars). The individual settings are instantiated from a single class, which contains code for storing and retrieving the settings. And, because different settings are in different formats (strings, integers, floats, etc.), it is a template class, and is capable of taking a value either in it's native data type or as a string and converting it to the appropriate data type for that template (using boost::lexical_cast). So, when I define a new setting, I can simply do something like: cvar<int> new_setting; new_setting = "100"; int i = new_setting.value; Now, my problem. In addition to the settings themselves, I also need to have a STL map which maps strings (containing the setting name) to the settings object itself. So, for example, a setting typed into the game console can be looked up by name and the object found, to read or write a value to: int i = settings["new_setting"]->value; The problem is the fact that the settings are template classes, so I can't exactly put a single type of pointer in the map and have it work for every instance. The only alternative I see is to use a base class that the settings derive from, and have the map use a base class pointer, but given how minimal the settings class is, I don't know what the base calss could possibly contain (there is no common code within the settings class that isn't using the template data type as a parameter). I suppose I could just use a void pointer and have something like: int i = reinterpret_cast<cvar<int> >(settings["new_setting"])->value; But that just looks terrible and opens up the potential for pointer problems. I hope this makes some sense. Ultimately, I'm trying to find a way for a central object to contain a pointer to a template class object, without knowing anything about the instances of the template class. So I can grab a pointer from one spot, and regardless of the data type of the object, call it's member functions to read and write data. Is this possible? Any ideas would be great.
Advertisement
You might check out boost::any and boost::variant.
"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke
You might also consider not associating the type with the setting itself, but instead with the code that accesses it. Just store a map of string to string, and then offer a templated accessor:

template <typename T>bool getSetting(T& result, const string& key) {  stringstream ss(theMap[key]);  return ss >> result;}int i;if (getSetting(i, "new_setting")) {  // yay}
I'm just going to point out that a base class with no code is called an interface, and that's a very useful construct (Java, for example, uses it extensively and has it has given the concept own language construct - sorry if you knew this already). I'd personally go for a solution involving an interface like you sketched out in your post.
Otherwise, as Agony said, no need to reinvent the wheel ;-)
In case you were wondering what to put in your next christian game; don't ask me, because I'm an atheist, an infidel, and all in all an antitheist. If that doesn't bother you, visit my site that has all kinds of small utilities and widgets.
Quote:Original post by Zahlman
You might also consider not associating the type with the setting itself, but instead with the code that accesses it. Just store a map of string to string, and then offer a templated accessor:

template <typename T>bool getSetting(T& result, const string& key) {  stringstream ss(theMap[key]);  return ss >> result;}int i;if (getSetting(i, "new_setting")) {  // yay}


Yeah, and looking at this solution it comes to mind, that you could even use it with your own classes - you only need to overload operator >> and that's it.
Nice.
Wow, that was fast, already several great ideas. I'll have to look these over and see how they fit, thanks a lot guys!
I've been looking over the ideas here, and I still have some concerns.

The Interface idea is the most attractive to me, because is seems fairly simple. However, again, I don't know what I could put into the base class that wouldn't turn it into a template. Every function within the settings class uses a template parameter. That would leave me with a base class containing absolutely nothing. Which the map happily accepts as a generic pointer, but which doesn't allow me to call the member functions of the settings which derive from the empty base class. Is there an alternative way to do this?

Performance is also a concern. Zahlman's idea of just storing each setting as a string and converting it to the appropriate data type when you need it using a template function is a novel idea, but also slow. The whole idea of originally craeting my settings class as a template was so that each data type could be stored natively, so that reading the value could be done without performance penalties. I plan to use these settings all over the place within the engine, so converting strings many many times per frame doesn't sound like a good idea.

It looks like boost::variant would pretty much do what I'm trying to accomplish with my settings class, but again it's complexity and body of features makes me wonder about run-time performance. Has anyone used them much? How much conversion does it do when working with the data? What penalties are there for reading the data back out as a particular type?

My other alternative is to forget the template idea, and just make the settings class contain multiple variables, one for each data type, and have it convert the written data to every format at once so that any one of them could be read back out.

cvar new_setting;
new_setting = "100";
int i = new_setting.integerValue;

Performance would be guaranteed, but this also just feels klunky, having to guess in advance what types will be needed and exposing them all at once. It might be my best bet, but I'm hoping there's still a way to make the template class idea work.
Quote:Original post by Nairou
Performance is also a concern. Zahlman's idea of just storing each setting as a string and converting it to the appropriate data type when you need it using a template function is a novel idea, but also slow. The whole idea of originally craeting my settings class as a template was so that each data type could be stored natively, so that reading the value could be done without performance penalties. I plan to use these settings all over the place within the engine, so converting strings many many times per frame doesn't sound like a good idea.

Well, yes, that doesn't sound like a good idea. But it is possible to solve this problem:
the hard way:
Try not to use Config directly. Read all values before game loop, store it in some struct(s).

the easy way:
Cache the values. Conversion from std::string to some other type will only happen when it is not cached (first request of the value or when the std::string changed).


I suppose you won't be trying to do it the hard way, so here's the sample of how would it work the easy way:
#include <string>#include <sstream>#include <iostream>#include <map>class ConfigValue{public:	ConfigValue(const std::string & val = "") : m_val(val), m_changed(true) {}	ConfigValue(const ConfigValue & val) : m_val(val.m_val), m_changed(true) {}	ConfigValue & operator = (const ConfigValue & val)	{		Set(val.m_val);		return *this;	}	ConfigValue & operator = (const std::string val)	{		Set(val);		return *this;	}	void Set (const std::string & val) { m_val = val; m_changed = true; }	// Auto-constructing from any type of value (except ConfigValue and std::string - those are already defined)	template< typename T >	ConfigValue (const T & val) : m_val(), m_changed(true)	{		std::ostringstream ss;		ss << val;		m_val = ss.str ();	}	// Auto-assigning from any type of value (except ConfigValue and std::string - those are already defined)	template< typename T >	ConfigValue & operator = (const T val)	{		std::ostringstream ss;		ss << val;		m_val = ss.str ();		m_changed = true;		return *this;	}	// Template functions that converts and caches the value	template< typename T >	T Get ()	{		static T cache;		if (m_changed)		{			std::istringstream ss (m_val);			ss >> cache;			m_changed = false;		}		return cache;	}	// No need to convert or cache anything, when the type is std::string	template<>	std::string Get ()	{		return m_val;	}private:	std::string m_val;	bool m_changed;};int main (){	std::map<std::string, ConfigValue> Config;	Config["FullScreen"] = true;	Config["WindowTitle"] = "My cool game";	Config["WindowWidth"] = 800;	Config["WindowHeight"] = 600;	Config["FPS"] = 71.3f;	bool fs = Config["FullScreen"].Get<bool> ();	std::string wt = Config["WindowTitle"].Get<std::string> ();	int ww = Config["WindowWidth"].Get<int> ();	int wh = Config["WindowHeight"].Get<int> ();	float fps = Config["FPS"].Get<float> ();	std::cout << "Full screen: " << fs << std::endl;	std::cout << "Window title: " << wt << std::endl;	std::cout << "Window width: " << ww << std::endl;	std::cout << "Window height: " << wh << std::endl;	std::cout << "FPS: " << fps << std::endl;	return 0;}


Note: the above is working example - i've tested it. ;)

Happy coding!
Awesome, thank you very much for such a detailed and interesting reply! I hadn't thought of doing it that way, it's definitely given me a lot more to think about in how I might implement it. I'll have to spend some time going over this. Thanks!
Quote:Original post by Nairou
Performance is also a concern.

Is it?
How many times per frame are you going to access your settings?
10? 100? 100,000?
Unless it's at least the latter, performance is not a concern. At least not until you've implemented it, profiled it, and found it to slow.

Quote:
It looks like boost::variant would pretty much do what I'm trying to accomplish with my settings class, but again it's complexity and body of features makes me wonder about run-time performance. Has anyone used them much? How much conversion does it do when working with the data?

No conversion. It's essentially a nice type-safe wrapper around a void pointer. It doesn't "convert" your data to anything, it just ensures that you don't insert an element as one datatype, and then extract it as another. And most of that overhead is placed at compiletime, so there's almost no runtime overhead in using boost::variant or boost::any

This topic is closed to new replies.

Advertisement