Using Managers
by Ebor Folkertsma

What is this article about?

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.

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.

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 T, int n> 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];	
};

(Note that this is only the interface. For information on implementation details, see the attached code files.)

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.

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 T, int n> 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<T, n>	*p_m_tManager;	
};

// Note that you MUST initialize these for correct functionality
template <class T, int n> int TPersistentManager<T, n>::refcount = 0;
template <class T, int n> TManager<T, n>* TPersistentManager<T, n>::p_m_tManager = 0x00000000;

template <class T, int n>
TPersistentManager<T, n>::TPersistentManager(void)
{
    if(refcount == 0)
    {
        p_m_tManager = new TManager<T, n>;
    }

    refcount++;
}


template <class T, int n> 
TPersistentManager<T, n>::~TPersistentManager(void)
{
    refcount--;

    if(refcount == 0)
    {
        delete p_m_tManager;
	  p_m_tManager = 0x00000000;
    }
}

template <class T, int n> 
int TPersistentManager<T, n>::Add(const T* p_T)
{
    return p_m_tManager->Add(p_T);
}

// .
// . Rest of code omitted… see the attached code files
// .

template <class T, int n> 
char *TPersistentManager<T, n>::GetErrorString()
{
    return p_m_tManager->GetErrorString();
}

Now this class does some crucial work, it makes sure that there is only one TManager pointer in the TPersistentManager template. It also redirects all functions to use that TManager class for performing its actions. Note that this redirection is needed because otherwise you cannot make your own class deriving from this so that it can use the functions. If you were to try that with the TManager it would just allocate new variables for each instance of the your class. Also note that it keeps a reference count to determine wheter the last instantiation of the class was released, in which case all memory for the managed objects is released as well.

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<CImage, 128>
{
public:
    CImageSystem();
    ~CImageSystem();

    . // Functions you want to make specific for the CImageSystem class
    .
    .
}

And now you can create multiple instances of the CImageSystem class in different files and they will still use the same CImageSystem class (which is the property of a singleton class).

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.

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: e.j.folkertsma@student.utwente.nl

Source

Here is the 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.

Discuss this article in the forums


Date this article was posted to GameDev.net: 5/29/2002
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Data Structures
Featured Articles

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!