in C++, how to use a singleton to ensure that each class has a unique integral ID?
Asked Answered
M

6

5

I have a bunch of C++ classes.

I want each class to have something like:

static int unique_id;

All instances of a same class should have the same unique_id; different classes should have different unique_id's.

The simplest way to do this appears to be threading a singleton through the classes.

However, I don't know what's called when for static class members / things that happen before main.

(1) if you have a solution that does not involve using singleton, that's fine too

(2) if you have a solution that gives me a :

int unique_id(); 

that is fine too.

Thanks!

Melodeemelodeon answered 31/1, 2010 at 18:59 Comment(4)
Do you need control over the value or are you just trying to differentiate objects of different classes? Can you just use the typeid keyword as a differentiator?Sheave
I don't need control over the value (not using this for serialization). I need need to differentiate objects of different classes. [Emulating Haskell's Data word in particular.]Melodeemelodeon
That's what std::type_info, the result of the typeid operator, was invented for. It even has the mechanics to be used as a map key.Discrimination
Be careful using this class ID to exchange information between machines, or serializating objects to/from a file. One machine may not generate IDs the same way as another. The generated IDs might even change on the same machine if you re-organize your code.Hovel
V
7

Have a class that increments it's ID on each creation. Then use that class as a static field in each object that is supposed to have an ID.

class ID
{
    int id;
public:
    ID() {
        static int counter = 0;
        id = counter++;
    }

    int get_id() {  return id; }
};

class MyClass
{
    static ID id;
public:
    static int get_id() 
    {
        return id.get_id();
    }
};
Venlo answered 31/1, 2010 at 19:2 Comment(10)
-1 Each instance of the class have the same value. Different classes have different values.Melodeemelodeon
@anon, don't downvote if you don't understand the solution, ask for a clarification.Venlo
You're right. This is my screw up. Can you "edit" your post so I can up vote you instead? (Vote too old to be changed).Melodeemelodeon
@anon, no offence taken, just don't jump the gun to early :)Venlo
@Korenl thanks for coming back and pointing out I misunderstood (instead of silently ignoring me); your solution is pretty cool.Melodeemelodeon
@anon, no problem I can understand that the the statement without code might have been hard to parse :PVenlo
You need to define the ID::id member. And class definition end with a ; in C++. :o> Also, you can put all the mechanics into its own class, so that all you need to do is derive from that. See https://mcmap.net/q/1927707/-in-c-how-to-use-a-singleton-to-ensure-that-each-class-has-a-unique-integral-id#2173034Discrimination
I think the counter in the ID constructor should be class level, not method level, mostly because I think the idea of a static in a member function, especially an inline one, is kind of confusing.Committal
...oh yeah, and the get_id() member(s) should probably be static.Discrimination
@sbi, I corrected what you pointed out. Thanks -- I wrote it from memory, hence the errors.Venlo
D
3

Building on Kornel's solution:

class id_impl {
  private:
    id_impl() {}
    static int get_next_id()
    {
      static int counter = 0;
      return ++counter;
    }
    template< class T >
    friend class id_base;
};

template< class T >
class id_base : private id_impl
{
  public:
    static int get_id() { return id; }
  private:
    static int id;
};

template< class T >
int id_base<T>::id id = get_next_id();

Use it like this:

class my_class : public id_base<my_class> {
  // ...
};
Discrimination answered 31/1, 2010 at 19:38 Comment(10)
What does the extra complication buy you?Committal
It's only a complication for the creator of id_base. For users of it, it's a simplification: they only need to derive from the class and don't have to copy that get_id() member into all their classes.Discrimination
Ahh, OK. It's the curiously recurring template idiom in order to save a lot of tedious work.Committal
Minor nitpicking, ID<T>::id_base id should be int id_base<T>::id. :)Pierrepierrepont
@gf: Thank you. (I started out by editing Kornel's answer and forgot to change that one...)Discrimination
+1: yes, that's a nice extension, I only provided the skeleton of the solution.Venlo
Your code does not compile, codepad.org/QipqYlPE you need to change int id_base<T>::id id = get_next_id(); to int id_base<T>::id = get_next_id(); a working version is here codepad.org/zckHKJHIZelma
@ sbi ... this is amazing. My coder has become much cleaner after this. Please explain to me, why does the "template< class T > int id_base<T>::id id = get_next_id();" not result in multiple copy of id_base<T>::id being defined?Melodeemelodeon
@anon: It does - one for each type. I thought that's what you wanted?Discrimination
@Beh Tou Cheh: Thank you for posting a corrected version of the code. I hadn't tried to compile it. Sorry for that.Discrimination
C
2

Actually that's very similar to RTTI. To achieve (2), C++'s buildin RTTI can be exploited. Call typeid on *this, and take the address of the typeinfo as unique ID.

Conss: a) IDs aren't be fixed (recompile would change them), and b) the information is only available given an instance of the class, c) it's ugly.

Why do you want this?

Comeau answered 31/1, 2010 at 19:11 Comment(0)
S
0

First, why? In any case, you can manually set the IDs easily:

template <int id>
struct base { enum { unique_id = id }; };

class foo: public base<5> { ... };
class bar: public base<10> { ... };

Then

foo x;
bar y;
assert(x.unique_id == 5);
assert(y.unique_id == 10);

Of course, you'll have to manually keep track of the IDs for each class; at this point, I'll ask the original question: why?

Suspend answered 31/1, 2010 at 19:9 Comment(0)
F
0

C++ has this already built in.

You can use the typeid operator to return a type_info class. The type_info:name() will return the (unique) name of the class.

Frater answered 31/1, 2010 at 19:10 Comment(4)
You will have to have RTTI enabled though.Frater
How do I get an integer instead of a string?Melodeemelodeon
@anon, type_info unfortuantely doesn't have such a field (a shame, really). You can typecast the type_info address to a integer, but that would be a fugly solution though.Venlo
Watch out. TTBOMK, the result of std::type_info::name() is unspecified. While all implementations I know return something that makes sense, I don't think the standard mandates that. An implementation could return "foo" or an empty string and still be standard-conforming.Discrimination
G
0

I have recently found sbi's version of Kornel's solution to be very useful. Thank you both for providing your answers. However, I wanted to extend the solution further so that several types of IDs can be easily created without creating a separate pair of id_impl and id_base classes for each new type.

To do this I templated the id_impl class, and added another argument to the id_base. The result is encapsulated in a header file that is included anywhere one wants to add a new ID type:

//idtemplates.h

template< class T >
class GeneralID 
{
  private:
    GeneralID() {}
    static int GetNextID()
    {
      static int counter = 0;
      return ++counter;
    }
    template< class T, class U >
    friend class GeneralIDbase;
};

template< class T, class U >
class GeneralIDbase : private GeneralID < T >
{
  public:
    static int GetID() { return ID; }
  private:
    static int ID;
};

template< class T, class U >
int GeneralIDbase<T, U>::ID = GetNextID();

For my application I wanted several abstract base classes to have an ID type associated with them. So for each instance of the GeneralIDbase template the types specified are: the abstract base class of the derived class being declared, and the derived class being declared.

The following main.cpp is an example:

//main.cpp    

#include<iostream>
#include<idtemplates.h>

using namespace std;

class MyBaseClassA {};
class MyBaseClassB {};

class MyClassA1 :public MyBaseClassA, public GeneralIDbase<MyBaseClassA, MyClassA1> {};
class MyClassA2 :public MyBaseClassA, public GeneralIDbase<MyBaseClassA, MyClassA2> {};
class MyClassB1 :public MyBaseClassB, public GeneralIDbase<MyBaseClassB, MyClassB1> {};
class MyClassB2 :public MyBaseClassB, public GeneralIDbase<MyBaseClassB, MyClassB2> {};

    int main()
{
    MyClassA1 objA1;
    MyClassA2 objA2;

    cout << "objA1.GetID() = "  << objA1.GetID() << endl;
    cout << "objA2.GetID() = "  << objA2.GetID() << endl;

    MyClassB1 objB1;
    MyClassB2 objB2;

    cout << "objB1.GetID() = "  << objB1.GetID() << endl;
    cout << "objB2.GetID() = "  << objB2.GetID() << endl;

    cin.get();
    return 0;
}

The output of this code is

/*
objA1.GetID() = 1 
objA2.GetID() = 2 
objB1.GetID() = 1 
objB2.GetID() = 2 
*/

I hope this helps! Please let me know of any issues.

Garfieldgarfinkel answered 29/7, 2012 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.