Skip to main content
GameDev.net gamedev.net
🔒 Locked

another of my ''fantastic'' templated storage classes

Started by speciesUnknown Nov 1, 2008 at 1:14 PM 10 replies 2.2k views
Original Post
speciesUnknown
speciesUnknown
Hi, I've implemented this class and written a unit test for it. The purpose of this class is to keep hold of a count of the number of users of a resource. The wrapper classes that use this core container are responsible for the rules of loading, which are a story for another day. I have a few questions: 1) How can I ensure this class is optimised? I dont want to do any "premature" optimisation, but Ive found in the past that STL has some strange quirks regarding what is faster that what. 2) This is only the second templated storage class I've written, are there any foot-shooting errors in here that I should be aware of? 3) Is my unit test exhaustative? Im aware that ive not tested getAllObjects, as this function is on probation. (read on) 4) can anybody think of a way to have the class delete each VALUE if and only if its a pointer? I want to put that in the deconstructor. Currently, im using a wrapper class for each type of resource that needs this particular system, and letting the wrapper class iterate through the results of getAllObjects(). I would rather not need to do this. 6) Is this "good" code, in terms of presentation and good practice? Is there anything I should be aware of / stop doing? I tried moving the inline functions into a .cpp file but got some annoying errors,
"error C2955: 'UseCountTable' : use of class template requires template argument list"
at the top of each function definition. googling the error got lots of results that seem unrelated to what im doing here.

#pragma once
#include <string>
#include <sstream>
#include <map>
#include <vector>
#include <utility>
#include <iostream>

#include "../Logging/Logging.h"

/*
	Templated storage of a set of objects along with a modifiable usage count.
*/

template <class VALUE> class UseCountTable
{
	public:
		UseCountTable()
		{
			
		}

		~UseCountTable()
		{
			
		}

		bool addMember(const std::string& name, int use_count, VALUE &object)
		{//return false if action cannot be performed
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it == objects.end())
			{
				objects[name]=std::pair<int,VALUE>(use_count,object);
				return true;
			}
			else
			{
				return false;
			}
		}

		bool updateMember(const std::string &name, int change)
		{//return false if action cannot be performed. 
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it != objects.end())
			{
				it->second.first += change;
				return true;
			}
			else
			{
				return false;
			}
		}

		bool deleteMember(const std::string& name)
		{//return false if action cannot be performed
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it != objects.end())
			{
				objects.erase(it);
				return true;
			}
			else
			{
				return false;
			}
		}

		bool exists(const std::string& name)
		{//return false if action cannot be performed
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it != objects.end())
			{
				return true;
			}
			else
			{
				return false;
			}
		}
		bool getMember(const std::string& name, int& use_count, VALUE& object)
		{//return false if action cannot be performed
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it != objects.end())
			{
				use_count = it->second.first;
				object = it->second.second;
				return true;
			}
			else
			{
				return false;
			}
		}

		bool getUseCount(const std::string& name, int& use_count)
		{//return false if action cannot be performed
			std::map<std::string, std::pair<int,VALUE> >::iterator it = objects.find(name);
			if(it != objects.end())
			{
				use_count = it->second.first;
				return true;
			}
			else
			{
				return false;
			}		
		}

		void consoleDump()
		{
			std::map<std::string, std::pair<int,VALUE> >::iterator 
				it = objects.begin(),
				end=objects.end();
			for(it; it != end; ++it)
			{
				std::cout<<"NAME:" <<it->first <<" USE COUNT:" <<it->second.first<<std::endl;
			}
		}
		void getAllObjects(std::vector<VALUE> &table)
		{
			std::map<std::string, std::pair<int,VALUE> >::iterator 
			it = objects.begin(),
			end=objects.end();
			for(it; it != end; ++it)
			{
				table.push_back(it->second);
			}
		}

	private:
		std::map<std::string, std::pair<int,VALUE> > objects;
};





Unit test code:

void UseCountTable_Test()
{
	struct TestObject{std::string name; std::string data; int integer;};
	UseCountTable<TestObject> t;
	
	// create 3 objects
	TestObject ob1,ob2,ob3;
	ob1.name="Object 1"; ob1.data="DATA!!!!!!"; ob1.integer=1;
	ob2.name="Object 2"; ob2.data="DATA@@@@@@"; ob2.integer=2;
	ob3.name="Object 3"; ob3.data="DATA££££££"; ob3.integer=3;	
	
	// insert them with use count of 1
	t.addMember("Object 1",1,ob1);
	t.addMember("Object 2",1,ob2);
	t.addMember("Object 3",1,ob3);
	t.consoleDump();
	
	// update object n with n more users
	t.updateMember("Object 1",1);
	t.updateMember("Object 2",2);
	t.updateMember("Object 3",3);
	t.consoleDump();

	// update objects with -1 users
	t.updateMember("Object 1",-1);
	t.updateMember("Object 2",-1);
	t.updateMember("Object 3",-1);
	t.consoleDump();

	// get object that should not be there, expect false
	TestObject ttest; int itest;
	std::cout<<(int) t.getMember("NOT THERE",itest,ttest);

	// get object that is there, expect true
	std::cout<<(int) t.getMember("Object 1",itest,ttest);

	// update object that should not be there, expect false
	std::cout<<(int) t.updateMember("hahaha",1);

	// update object that is there, exect true
	std::cout<<(int) t.updateMember("Object 1",1);

	// delete object not there, expect false
	std::cout<<(int) t.deleteMember("not there");

	// delete object that is there, expect true
	std::cout<<(int) t.deleteMember("Object 1");
	
	// delete same object again, expect false
	std::cout<<(int) t.deleteMember("Object 1");
	
	// get usecount of object that is there, expect true
	std::cout<<(int) t.getMember("Object 2",itest,ttest);		
	
	// try to get that object, expect false
	std::cout<<(int) t.getMember("Object 1",itest,ttest);
}





Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Zakwayda
Zakwayda
What are the 'use counts' for, exactly?

A few other random observations:

1. I'm not used to seeing template argument names in all caps (maybe that's just me though).

2. Why have you implemented an empty constructor and an empty non-virtual destructor? The compiler-generated versions of these functions will be equivalent.

3. This:
			if(it != objects.end())			{				return true;			}			else			{				return false;			}
Can be written as:
return it != objects.end();
4. Using typedef's where appropriate would clean up your code considerably.

5. You might take a look at the SC++L utility function make_pair().

6. It looks like you've missed a couple of opportunities for constant-correctness (specifically, you're passing some arguments by non-constant reference, but they are never modified within the function).
speciesUnknown
speciesUnknown
Thanks for your advice.

Quote:
Original post by jyk
What are the 'use counts' for, exactly?

To cut a long story short, resources are loaded and unloaded during runtime. There are also dependencies between resources, for example a Mesh depends on several Brushes and a Brush depends on several Materials. but there are also many to many dependencies, such as textures or shaders that are shared by various materials. (e.g. a red brick Material and a grey brick Material, both with the same bump map and same shader.). Rather than having a join table of some kind, I've decided to count resources in and out and then update their depenencies with what uses them.

The resource manager will be able to use any metric I wish to 1) shuffle resources in and out of GPU ram, and b) remove a resource from the CPU side if its no longer needed.

Now that i think about it, it should be UserCountTable rather than UseCountTable.

Quote:


A few other random observations:

1. I'm not used to seeing template argument names in all caps (maybe that's just me though).

I always use TYPE, INDEX, KEY, VALUE etc to demonstrate what each templated type does. Some just like to use T V K etc but thats not so descriptive.
Quote:

2. Why have you implemented an empty constructor and an empty non-virtual destructor? The compiler-generated versions of these functions will be equivalent.

Just a bad habit. /wristslap
Quote:

3. This:
			if(it != objects.end())			{				return true;			}			else			{				return false;			}
Can be written as:
return it != objects.end();



4. Using typedef's where appropriate would clean up your code considerably.

5. You might take a look at the SC++L utility function make_pair().

6. It looks like you've missed a couple of opportunities for constant-correctness (specifically, you're passing some arguments by non-constant reference, but they are never modified within the function).

a couple? I've fixed the one on addMember but dont see another one.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
Zakwayda
Zakwayda
Quote:
To cut a long story short, resources are loaded and unloaded during runtime. There are also dependencies between resources, for example a Mesh depends on several Brushes and a Brush depends on several Materials. but there are also many to many dependencies, such as textures or shaders that are shared by various materials. (e.g. a red brick Material and a grey brick Material, both with the same bump map and same shader.). Rather than having a join table of some kind, I've decided to count resources in and out and then update their depenencies with what uses them.

The resource manager will be able to use any metric I wish to 1) shuffle resources in and out of GPU ram, and b) remove a resource from the CPU side if its no longer needed.
I can't help but think you could implement the same functionality using (e.g.) the Boost smart pointer libraries. I realize you want to do more here than simply manage the lifetime of your objects, but before committing to your current approach I'd at least take a look at the following:

1. shared_ptr::use_count(). This function will tell you how many times a given object is currently referenced. The documentation cautions that this function may not be efficient and should not be used for production code, so maybe it should be avoided - I'm not sure. (AFAIK it only involves a couple of dereferences and a virtual function call, but I haven't looked at the code too closely; there may be more going on there than I'm aware of.)

2. boost::intrusive_ptr. With intrusive_ptr, you manage your own reference count, which means you can do pretty much anything you want in response to adding or deleting of references.

Also, it looks like you're passing resources around by value, which is expensive, and isn't typically how shared resources are handled (at least not in my experience). I think it would make more sense to store and pass around your resources by reference (e.g. smart pointer).
Quote:
I always use TYPE, INDEX, KEY, VALUE etc to demonstrate what each templated type does. Some just like to use T V K etc but thats not so descriptive.
I'm not suggesting changing to single-letter template argument names, but rather using normal capitalization, e.g. 'Type', 'Index', etc.
Quote:
a couple? I've fixed the one on addMember but dont see another one.
I don't see any others - maybe that was the only one.
speciesUnknown
speciesUnknown
I considered the boost pointer classes after being recommended it elsewhere, but I've decided to go with this system as I need more control than just knowing how many times something is referenced.

Thanks for the advice. I've made a few changes to the code to match your recommendations.
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
_goat
_goat
1) Consider using a unit-testing framework for your tests. I heartily recommend UnitTest++ (man that page gives off an unprofessional appearence), although others are also good.

2) Consider using an unsigned type for your reference count. It communicates the idea to your users better.

3) What you're writing is basically a cache. Consider writing a cache instead of what you have (ie, the templated cache is responsible for the loading of the objects, their lifetimes, etc).

4) Seriously, consider using shared_ptrs.
speciesUnknown
speciesUnknown
Quote:
Original post by _goat
1) Consider using a unit-testing framework for your tests. I heartily recommend UnitTest++ (man that page gives off an unprofessional appearence), although others are also good.

2) Consider using an unsigned type for your reference count. It communicates the idea to your users better.

I am the only user. True, my class may be used by somebody else but its designed for a very specific purpose, as the core functionality of several similar classes.
Quote:

3) What you're writing is basically a cache. Consider writing a cache instead of what you have (ie, the templated cache is responsible for the loading of the objects, their lifetimes, etc).


Each resource table (cache if you will) is in fact a type specific wrapper for this class. For example, the Material table which has several files to load when it builds a material, compared with the texture table which only has to load a single texture. They all use the same interface,

processShoppingList()
getPtr()
flush()
collect(Predicate)

Is that what you were thinking of?
Quote:


4) Seriously, consider using shared_ptrs.


This is the third time I've been told to use shared_ptr so im going to spend a day playing with them. (first this means building boost, and im not sure my dying windows install is up to it)

Thanks again.

[/quote]
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
SiCrane
SiCrane
Your unit test isn't actually legal C++. Function local types have internal linkage and templates can only be instantiated with types with external linkage.
speciesUnknown
speciesUnknown
Quote:
Original post by SiCrane
Your unit test isn't actually legal C++. Function local types have internal linkage and templates can only be instantiated with types with external linkage.


Why did it compile? (MSVC++9)
Should I create a test harness class instead?
Don't thank me, thank the moon's gravitation pull! Post in My Journal and help me to not procrastinate!
SiCrane
SiCrane
It compiled because you're using language extensions. Use the /Za switch to disable extensions and it should fail to compile. All you need to do to make it legal is to move the TestObject definition outside of the function body.
johdex
johdex
Quote:

4) can anybody think of a way to have the class delete each VALUE if and only if its a pointer? I want to put that in the deconstructor. Currently, im using a wrapper class for each type of resource that needs this particular system, and letting the wrapper class iterate through the results of getAllObjects(). I would rather not need to do this.


You could use template specialization (disclaimer: I don't remember the exact syntax for specialization).

template<class T>void dealloc(T){// Default implementation does nothing.}template<>void dealloc(T* ptr){    delete ptr;}


Then call dealloc() from your deleteMember() method.

That should answer the technical aspect of your question, but from a style point of view, it's not so nice to have code deallocate things allocated somewhere else, especially when it does so for some types but not for others.

In addition to that, my suggestion assumes ptr was allocated using new(), which might not always be the case.
-- Top10 Racing Simulation needs more developers!http://www.top10-racing.org
Zakwayda
Zakwayda
Quote:
This is the third time I've been told to use shared_ptr so im going to spend a day playing with them. (first this means building boost, and im not sure my dying windows install is up to it)
Just FYI, you don't have to build the Boost libraries to use the 'smart pointer' classes; just include the appropriate headers, and you're good to go.

And again, I recommend at least taking a look at intrusive_ptr in addition to shared_ptr (given that it sounds like you want to exercise some control over exactly how reference counting is handled).

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.