This article is about a nice way to manage all kinds of data structures in an easy way. I will discuss how to make a templated singleton manager. For those people who do not know what the first two words mean, there are a lot of nice tutorials about these on the internet. And it is recommended to first understand what templates and singletons are in order to fully understand this article. I will not discuss them here because they are programming concepts which should be familiar to the serious programmer.
[size="5"]Purpose
The purpose of this article is to learn an approach to program stable managers which provide basic functions that never need to be rewritten. So what does that mean for you? You could use this approach for managing your own data structures and avoid wasting time and effort repeatedly writing the same type of code. You could use this approach for managing all kinds of data structures, such as images, textures, sounds, meshes, and so on.
[size="5"]How to do a 'normal' template manager
First I will explain how you can create a 'normal' template manager, because it will be much easier to understand the singleton version later. I will start with a piece of code and then explain the relevant portions:
template class TManager
{
public:
TManager(void);
~TManager(void);
int Add(const T* p_T);
int Delete(const T* p_T);
int Delete(int index);
int DeleteAll();
T* Get(int index);
T* Get(const T* p_T);
Int GetCount();
int GetError();
char *GetErrorString();
protected:
int error;
T* p_T[n];
};
Well that really is everything there is to the interface. Most functions perform rather obvious operations, although the last three functions might seem strange. These functions really aren't required but I used them anyway. The GetCount() function returns the maximum number of objects that can be managed by this class. The GetError() function returns an error code if one of the previously called functions failed. Normally I just give functions two types of return values - one indicates success and one indicates failure. On failure I then call the GetError() function to retrieve the error. Note that this is much the same way OpenGL and other standard APIs work. The GetErrorString() function returns an appropriate description for the error (which might come in handy for debugging or logging an application). The key to understanding what is done here is to see that this template 'generates' code, the code that you specified for the functions, when you make a class inherit from it. Note that the 'T' stands for the class type you want to use and that 'n' stands for the number of objects you want to be able to manage at most.
When looking at the source code (attached to this document) the smart people will see that I do something rather suspicious. In some methods I compare or assign a class using standard '==' and '=' operators. Therefore, it is important that you overload these operators for the classes you want to use with the manager. But since overloading those operators is very easy, I will let that be an exercise for you.
[size="5"]Make it a 'singleton' template manager
Well now that you understand how the 'normal' template works, here is the implementation of the 'singleton' template:
template class TPersistentManager
{
public:
TPersistentManager(void);
~TPersistentManager(void);
int Add(const T* p_T);
int Delete(const T* p_T);
int Delete(int index);
int DeleteAll();
T* Get(int index);
T* Get(const T* p_T);
Int GetCount();
int GetError();
char *GetErrorString();
private:
static int refcount;
static TManager *p_m_tManager;
};
// Note that you MUST initialize these for correct functionality
template int TPersistentManager::refcount = 0;
template TManager* TPersistentManager:_m_tManager = 0x00000000;
template
TPersistentManager::TPersistentManager(void)
{
if(refcount == 0)
{
p_m_tManager = new TManager;
}
refcount++;
}
template
TPersistentManager::~TPersistentManager(void)
{
refcount--;
if(refcount == 0)
{
delete p_m_tManager;
p_m_tManager = 0x00000000;
}
}
template
int TPersistentManager::Add(const T* p_T)
{
return p_m_tManager->Add(p_T);
}
// .
// . Rest of code omitted... see the attached code files
// .
template
char *TPersistentManager::GetErrorString()
{
return p_m_tManager->GetErrorString();
}
[size="5"]How to use it for your own types
Okay that is all nice, but now the question remains: "How can I use it?" The answer to that question is rather simple: you just instantiate a class the same way you would instantiate a normal template class. Here is an example to make it clear:
class CImageSystem : public TPersistentManager
{
public:
CImageSystem();
~CImageSystem();
. // Functions you want to make specific for the CImageSystem class
.
.
}
[size="5"]Evaluation
Although this code might seem a bit messy (and I cannot deny it is) it allows you to easily create managers for all kind of data structures without having to rewrite code. And although the extra function calling in the TPersistentManager class might seem like overhead (and it is), if you have a good compiler it will inline these functions for you, resulting in close to 0% function calling overhead in some cases.
[size="5"] Conclusion
Well I do not think that I can say anything here that isn't obvious yet. There are really only two choices. You can use this approach, or you can use another approach. I really do not want to say that this is the best way to manage data, but at least it is a way (and in my opinion a rather good one).
If there are any people out there with suggestions, comments or remarks, please feel free to email me at: [email="e.j.folkertsma@student.utwente.nl"]e.j.folkertsma@student.utwente.nl[/email]
[size="5"]Source
Note that this probably isn't the best code possible. I wrote it in 60 minutes or so with some retouching (since I didn't mean to send it along in the first place) to make it readable.