In a previous post, I gave you the basics for template functions in C++. One of the issues I find when I have a problem and am looking for examples of the solution is that many times the examples are so basic that they don’t really demonstrate the solution. Today, I want to show you a complex solution that I had to create using templates. Hopefully this will give you a better idea of what we can do.
In my real-world case, I had a potential file system of files and a database. Files had to be defined in a data structure (read from Xml), traversed and put into a database. The database contained different table definitions for different types of files (binary files, document files, etc.).
At first, the data structures looked like this:
class CBinaryFile
{
public:
// Data
typedef CList<CBinaryFile> List;
};
class CContentFile
{
public:
// Data
typedef CList<CContentFile> List;
};
class CFolder
{
public:
CBinaryFile::List BinaryFiles;
CContentFile::List ContentFiles;
typedef CList<CFolder> List;
CFolder::List Folders;
};
CFolder::List TheFolders;
You’ll notice that this is a recursive list of Folders that contains binary files and content files. However, additional requirements came into play: in memory, the binary file hierarchy needed to be separated from the content file hierarchy. This means that I needed to have one set of folders for binary files and a different set of folders for content files. I could have very well kept the data structure the same, but just had two top-level variables for the folders:
CFolder::List BinaryFolders;
CFolder::List ContentFolders;
Using this, I’d basically have wasted memory. No one likes that :)
So I changed to use templates. My CFolder class changed to this:
template <class T>
class CFolder
{
T::List Files;
typedef CList< CFolder<T> > List;
CFolder::List Folders;
};
This way, my two top-level variables become:
CFolder<BinaryFile>::List BinaryFolders;
CFolder<ContentFile>::List BinaryFolders;
So far though, all I’ve shown you was class templates. Where does the function template come into the picture?
I now need to traverse the folders and process the files into the database. I did not want to add the functions to the class. I’ll explain later.
void Traverse(CFolder<BinaryFile>::List &folders)
{
// ...
}
void Traverse(CFolder<ContentFile>::List &folders)
{
// ...
}
Again, duplication of code. And we don’t want that either. So I did this:
template <class T>
void Traverse(CFolder<T>::List &folders)
{
// ...
}
This gave me a single function that does the work of traversing the folders. But what about the end? The 2 classes needed to be processed differently. For this, I created an abstract item handler class that was implemented for each file type. Here is the final code:
class CBinaryFile
{
public:
// Data
typedef CList<CBinaryFile> List;
};
class CContentFile
{
public:
// Data
typedef CList<CContentFile> List;
};
template <class T>
class CFolder
{
public:
typename T::List Files;
typedef CList< CFolder<T> > List;
List Folders;
};
template<class T>
class IItemHandler
{
public:
virtual void ProcessItem(T &item) = 0;
};
class BinaryFileHandler : public IItemHandler<CBinaryFile>
{
public:
void ProcessItem(CBinaryFile &item)
{
// Insert into the database
}
};
class ContentFileHandler : public IItemHandler<CContentFile>
{
public:
void ProcessItem(CContentFile &item)
{
// Insert into the database
}
};
template <class T>
void Traverse(typename CFolder<T>::List &folders,
IItemHandler<T> *pHandler)
{
POSITION pos = folders.GetHeadPosition();
while (pos != NULL)
{
CFolder<T> &folder = folders.GetNext(pos);
// Process our items
POSITION p2 = folder.Items.GetHeadPosition();
while (p2 != NULL)
{
T &item = folder.Items.GetNext(pos);
pHandler->ProcessItem(item);
}
// Do the sub-folders
Traverse(folder.Folders, pHandler);
}
}
BinaryFileHandler bfh;
Traverse(binaryFolders, &bfh);
ContentFileHandler cfh;
Traverse(contentFolders, &cfh);
Sometimes, it seems like alot of work and code just to avoid duplicating code or wasting memory. These are judgement calls. The nice thing is that if I needed to add another file type later on, the groundwork is already done. All I need to do is define my file type class and then create the handler class to insert it into the database. Obviously, the more file types that are needed, the better looking this solution is. It’s questionable whether this work is beneficial for only 2 file types. But if you had 25 or 50, I think it’s obvious that this solution is better than code duplication. Especially if you find a bug in the traversal code :)