Giving Each Instance of a Class a Unique Identifier
Problem
You want each object of a class to have a unique identifier.
Solution
Use a static member variable to keep track of the next available identifier to use. In the constructor, assign the next available value to the current object and increment the static member. See Example 8-8 to get an idea of how this works.
Example 8-8. Assigning unique identifiers
#include class UniqueID { protected: static int nextID; public: int id; UniqueID( ); UniqueID(const UniqueID& orig); UniqueID& operator=(const UniqueID& orig); }; int UniqueID::nextID = 0; UniqueID::UniqueID( ) { id = ++nextID; } UniqueID::UniqueID(const UniqueID& orig) { id = orig.id; } UniqueID& UniqueID::operator=(const UniqueID& orig) { id = orig.id; return(*this); } int main( ) { UniqueID a; std::cout << a.id << std::endl; UniqueID b; std::cout << b.id << std::endl; UniqueID c; std::cout << c.id << std::endl; }
Discussion
Use a static variable to keep track of the next identifier to use. In Example 8-8, I used a static int, but you can use anything as the unique identifier, so long as you have a function that can generate the unique values.
In this case, the identifiers are not reused until you reach the maximum size of an int. Once you delete an object, that object's unique value is gone until the program restarts or the identifier value maxes out and flips over. This uniqueness throughout the program can have some interesting advantages. For example, if you're working with a memory management library that shuffles memory around and invalidates pointers, you can be assured that the unique value will remain the same per object. If you use the unique values in conjunction with Recipe 8.4, but use a map instead of a list, you can easily locate your objects given the unique identifier. To do this, you would simply map unique IDs to object instances, like so:
static map instmap;
This way, any code that keeps track of an object's identifier can find it later without having to maintain a reference to it.
But that's not the whole story. Consider the case where you need to add one of these objects to a standard container (vector, list, set, etc.). The standard containers store copies of the objects you add to them, not references or pointers to the objects themselves (unless, of course, it is a container of pointers). Thus, the standard containers expect objects they contain to behave as value objects, which means objects that, when assigned with the assignment operator, or copied with a copy constructor, create new versions that are equal to the old versions.
This means that you have to make a decision on how you want your unique objects to behave. When you create an object with a unique identifier and add it to a container you then have two objects with the same identifier unless you've done something different in your assignment operator. You need to deal with the unique value in your assignment operator and copy constructor in a way that makes sense. Does it make sense for the object in the container to be equal to the original object? If so, the standard copy constructor and assignment operators will get the job done, but you should be explicit so users of your class know you did it that way on purpose and didn't just forget how containers work and get lucky. For example, to use the same identifier values your copy constructor and assignment operator would look like this:
UniqueID::UniqueID(const UniqueID& orig) { id = orig.id; } UniqueID& UniqueID::operator=(const UniqueID& orig) { id = orig.id; return(*this); }
But maybe it makes more sense to create another unique value for the object in the container in the context of your application. In that case, just use the static variable again as you did in the ordinary constructor, like this:
UniqueID::UniqueID(const UniqueID& orig) { id = ++nextID; } UniqueID& UniqueID::operator=(const UniqueID& orig) { id = ++nextID; return(*this); }
You may still not be in the clear though. If UniqueID will be used by multiple threads, you are going to run into trouble because access to the static variable is not synchronized. See Chapter 12 for more information on making resources usable by multiple threads.
See Also
Recipe 8.3