I’m trying to avoid declaring enums or using strings. Although the rationale to do so may seem dubious, the full explanation is irrelevant.
My question is fairly simple. Can I use the address of a member variable as a unique ID?
More specifically, the requirements are:
- The ID won’t have to be serialised.
- IDs will be protected members – only to be used internally by the owning object (there is no comparison of IDs even between same class instances).
- Subclasses need access to base class IDs and may add their new IDs.
So the first solution is this:
class SomeClass
{
public:
int mBlacks;
void AddBlack( int aAge )
{
// Can &mBlacks be treated as a unique ID?
// Will this always work?
// Is void* the right type?
void *iId = &mBlacks;
// Do something with iId and aAge
// Like push a struct of both to a vector.
}
};
While the second solution is this:
class SomeClass
{
public:
static int const *GetBlacksId()
{
static const int dummy = 0;
return &dummy;
}
void AddBlack( int aAge )
{
// Do something with GetBlacksId and aAge
// Like push a struct of both to a vector.
}
};
No other
intdata member of this object, and nomBlacksmember of a different instance ofSomeClassin the same process, has the same address as themBlacksmember of this instance ofSomeClass. So you’re safe to use it as a unique ID within the process.An empty base class subobject of
SomeClasscould have the same address asmBlacks(ifSomeClasshad any empty base classes, which it doesn’t), and thecharobject that’s the first byte ofmBlackshas the same address asmBlacks. Aside from that, no other object has the same address.void*will work as the type.int*will work too, but maybe you want to use data members with different types for different ids.However, the ID is unique to this instance. A different instance of the same type has a different ID. One of your comments suggests that this isn’t actually what you want.
If you want each value of the type to have a unique ID, and for all objects that have the same value to have the same ID, then you’d be better of composing the ID from all of the significant fields of the object. Or just compare objects for equality instead of their IDs, with a suitable
operator==andoperator!=.Alternatively if you want the ID to uniquely identify when a value was first constructed other than by copy constructors and copy assignment (so that all objects that are copies of the same “original” share an ID), then the way to do that would be to assign a new unique ID in all the other constructors, store it in a data member, and copy it in the copy constructor and copy assignment operator.
The canonical way to get a new ID is to have a global[
*] counter that you increment each time you take a value. This may need to be made thread-safe depending what programs use the class (and how they use it). Values then will be unique within a given run of the program, provided that the counter is of a large enough type.Another way is to generate a 128 bit random number. It’s not theoretically satisfying, but assuming a decent source of randomness the chance of a collision is no larger than the chance of your program failing for some unavoidable reason like cosmic ray-induced data corruption. Random IDs are easier than sequential IDs when the sources of objects are widely distributed (for example if you need IDs that are unique across different processes or different machines). You can if you choose use some combination of the MAC address of the machine, a random number, the time, a per-process global[
*] counter, the PID and anything else you think of and lay your hands on (or a standard UUID). But this might be overkill for your needs.[
*] needn’t strictly be global – it can be a private static data member of the class, or a static local variable of a function.