14

I would appreciate any help as C++ is not my primary language.

I have a template class that is derived in multiple libraries. I am trying to figure out a way to uniquely assign an id int to each derived class. I need to be able to do it from a static method though, ie.


template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
    }
    // ...
};
Thank you!
Nash
  • 922
  • 2
  • 10
  • 18

11 Answers11

11

This can be done with very little code:

template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        return reinterpret_cast<int>(&s_id);
    }
};
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 5
    Pointers are not guaranteed to fit in an int. Consider the optional type uintptr_t or some implementation-defined integral type of sufficient size. – Lars Viklund Mar 20 '12 at 08:06
  • @LarsViklund: It's not necessary to hold the entire pointer value. Unless your code is more than 4GB, the low 32 bits of the function address will be unique. – Ben Voigt May 20 '14 at 18:14
  • 4
    Just tried this in Visual Studio 2015 and while it works like a charm in Debug mode, due to optimizations all classes that implement this trick return the exact same id in Release mode. – Paul Houx Dec 19 '15 at 23:17
  • @paul try disabling the particular MS-specidic optimization that makes it non conforming. I think `/ICF` is the culprit. – Ben Voigt Dec 19 '15 at 23:30
  • 2
    Basically, identical function folding is a good idea, but the optimizer is supposed to disable it if the function's address is taken anywhere. Microsoft linker doesn't obey that rule. – Ben Voigt Dec 19 '15 at 23:32
  • Thanks for the additional info, Ben. Instead of changing the optimizer settings, I chose to use a solution using type_id(class).hash_code() instead. – Paul Houx Dec 20 '15 at 00:43
  • This code doesn't protect against memory reuse. If an object is freed and another object is created in its place, it would have the same ID, contrary to reasonable expectations. – proski Sep 10 '18 at 21:15
  • 2
    @proski: I think you misunderstood the question -- this provides a unique ID per type, not per instance. – Ben Voigt Sep 11 '18 at 00:14
  • Indeed, I was looking for an _object_ ID implementation (preferably standard and well tested) and just assumed this thread was about it. My bad. – proski Sep 11 '18 at 18:34
9

In the modern C++ (03 - assuming you're using a recent compiler like gcc) you can use the typeid keyword to get a type_info object that provides basic type informations at least at runtime - that's a standard (and then cross-platform) feature.

I took the example from wikipedia and added a template/inheritance check, it seems to works well but i'm not certain for the int version (that is a hack exploiting the assumption that the compiler will have the types names somewhere in a read only memory space...that might be a wrong assumption).

The string identifier seems far better for cross-platform identification, if you can use it in you case. It's not cross-compiler compatible as the name it gives you is "implementation defined" by the standard - as suggested in comments.

The full test application code:

#include <iostream>
#include <typeinfo>  //for 'typeid' to work

class Person 
{
public:
   // ... Person members ...
   virtual ~Person() {}
};

class Employee : public Person 
{
   // ... Employee members ...
};

template< typename DERIVED >
class Test
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
        // NOT SURE IT WILL BE REALLY UNIQUE FOR EACH CLASS!!
        static const int id = reinterpret_cast<int>(typeid( DERIVED ).name());
        return id;
    }

    static const char* s_name()
    {
        // return id unique for DERIVED
        // ALWAYS VALID BUT STRING, NOT INT - BUT VALID AND CROSS-PLATFORM/CROSS-VERSION COMPATBLE
        // AS FAR AS YOU KEEP THE CLASS NAME
        return typeid( DERIVED ).name();
    }
};

int wmain () 
{
    Person person;
    Employee employee;
    Person *ptr = &employee;



    std::cout << typeid(person).name() << std::endl;   // Person (statically known at compile-time)
    std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)
    std::cout << typeid(ptr).name() << std::endl;      // Person * (statically known at compile-time)
    std::cout << typeid(*ptr).name() << std::endl;     // Employee (looked up dynamically at run-time
                                                    // because it is the dereference of a pointer to a polymorphic class)

    Test<int> test;
    std::cout << typeid(test).name() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_id() << std::endl;    
    std::cout << test.s_name() << std::endl;    

    Test< Person > test_person;
    std::cout << test_person.s_name() << std::endl;    
    std::cout << test_person.s_id() << std::endl;    

    Test< Employee > test_employee;
    std::cout << test_employee.s_name() << std::endl;    
    std::cout << test_employee.s_id() << std::endl;    

    Test< float > test_float;
    std::cout << test_float.s_name() << std::endl;    
    std::cout << test_float.s_id() << std::endl;    


    std::cin.ignore();
    return 0;
}

Outputs :

class Person
class Employee
class Person *
class Employee
class Test<int>
3462688
3462688
3462688
int
class Person
3421584
class Employee
3462504
float
3462872

This works at least on VC10Beta1 and VC9, should work on GCC. By the way, to use typeid (and dynamic_cast) you have to allow runtime type infos on your compiler. It should be on by default. On some plateform/compiler (I'm thinking about some embedded hardwares) RTTI is not turned on because it have a cost, so in some extreme cases you'll have to find a better solution.

Klaim
  • 67,274
  • 36
  • 133
  • 188
  • 2
    I like the reinterpret_cast of the pointer; hadn't thought of that. It should be unique for every class (technically, reinterpret cast doesn't guarantee that the ints should be separate, but guarantees reversibility, which would logically imply uniqueness) However, the string might not be cross platform; the name it gives you is "implementation defined" by the standard, and I have used platforms where it hands you back the mangled name. – Todd Gardner May 28 '09 at 23:25
  • This is non-standard and not safe. According to C++ standard, different class can have the same `name` in their `type_info`. A "smart" compile could easily decide to merge those identical C strings into the same instance (and thus to have the same address) in attempt of optimization. – xiay Jan 30 '19 at 03:17
  • @logicor Indeed. They are not currently but that's not a perfect solution. Wow that question/answer is old XD – Klaim Feb 26 '19 at 17:00
2

I'm not 100% happy with the answers so far and I have worked out one solution for me. The idea is to compute a hash of the type name using typeinfo. Everything is done once when loading the application so there's no runtime overload. This solution will work also using shared libraries as the type name and the hash of it will be consistent.

This is the code I use. This works great for me.

#include <string>
#include <typeinfo>
#include <stdint.h>

//###########################################################################
// Hash
//###########################################################################
#if __SIZEOF_POINTER__==8
inline uint64_t hash(const char *data, uint64_t len) {
    uint64_t result = 14695981039346656037ul;
    for (uint64_t index = 0; index < len; ++index)
    {
        result ^= (uint64_t)data[index];
        result *= 1099511628211ul;
    }
    return result;
}
#else
inline uint32_t hash(const char *data, uint32_t len) {
    uint32_t result = 2166136261u;
    for (uint32_t index = 0; index < len; ++index)
    {
        result ^= (uint32_t)data[index];
        result *= 16777619u;
    }
    return result;
}
#endif
inline size_t hash(const std::string & str) { return hash(str.c_str(), str.length()); }

//###########################################################################
// TypeId
//###########################################################################
typedef size_t TypeId;

template<typename T>
static const std::string & typeName() {
    static const std::string tName( typeid(T).name() );
    return tName;
}
template<typename T>
static TypeId typeId() {
    static const TypeId tId = hash( typeName<T>() );
    return tId;
}
Alex Moreno
  • 151
  • 1
  • 8
2
#include <stdint.h>
#include <stdio.h>

#define DEFINE_CLASS(class_name) \
    class class_name { \
    public: \
        virtual uint32_t getID() { return hash(#class_name); } \

// djb2 hashing algorithm
uint32_t hash(const char *str)
{
    unsigned long hash = 5381;
    int c;

    while ((c = *str++))
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

DEFINE_CLASS(parentClass)

    parentClass() {};
    ~parentClass() {};
};

DEFINE_CLASS(derivedClass : public parentClass)

    derivedClass() : parentClass() {};
    ~derivedClass() {};
};

int main() {
    parentClass parent;
    derivedClass derived;
    printf("parent id: %x\nderived id: %x\n", parent.getID(), derived.getID());
}
user6567423
  • 353
  • 4
  • 9
2

In my previous company we did this by creating a macro which would take the class name as a parameter, create a local static with the unique id (based on class name) and then create an override of a virtual function declared in the base class that returned the static member. That way you can get the ID at runtime from any instance of the object hierarchy, similar to the 'getClass()' method in a java object, though much more primitive.

Jherico
  • 28,584
  • 8
  • 61
  • 87
1

The below snippet works in VS(2015) and Release builds:

template <typename T>
struct TypeId
{
  static size_t Get()
  {
    return reinterpret_cast<size_t>(&sDummy);
  }

private:
  static char sDummy;
};

template <typename T>
char TypeId<T>::sDummy; // don't care about value

Also tried and tested on GCC v7.3 (Ubuntu 16.04) and LLVM v10.0.0 (Mac OS High Sierra).

How it works: each instantiation of the TypeId<> template gets its own, unique sDummy instance with its own, unique address. To be honest I'm not entirely sure why the function-static version didn't work in release -- I suspect identical comdat folding and optimizations.

Exercise for the reader: at least const and ref types should get the same type ID as the raw type.

zyndor
  • 1,418
  • 3
  • 20
  • 36
  • Just tested this, works great! Would you mind explaining why this works please? – DavidColson Aug 17 '19 at 23:45
  • 1
    @DavidColson I've attempted to explain workings (...not a minute too early). Hope this helps.=) I'll endeavour to update the answer once I've got a clearer idea. – zyndor Oct 16 '19 at 05:09
1

What kind of ID? Are you looking for an atomically increasing int? If a string is fine, what about:

static string s_id()
{
   return typeid(Foo<DERIVED>).name();
}

If it needs to be an int, but not automatically increasing, you could hash that for a 128-bit integer unlikely to have collisions (though likely a larger number than you need)

Todd Gardner
  • 13,313
  • 39
  • 51
1

Here's what I ended up doing. If you have any feedback (pros, cons) please let me know.


template < class DERIVED >
class Foo
{
public:
    static const char* name(); // Derived classes will implement, simply
// returning their class name static int s_id() { static const int id = Id_factory::get_instance()->get_id(name()); return id; } // ... };

Essentially the id will be assigned after doing a string comparison rather than a pointer comparison. This is not ideal in terms of speed, but I made the id static const so it will only have to calculate once for each DERIVED.

Nash
  • 922
  • 2
  • 10
  • 18
0

As Paul Houx pointed out here, trick with returning an address of static method may not work due to compiler optimizations. But we can make a workaround using __FILE__ and __LINE__ macros + volatile keyword.

The final solution will look something like this:

#define GET_TYPE_ID static size_t GetTypeId()   \
{                                               \
    volatile const char* file = __FILE__;       \
    volatile uint32_t line = __LINE__;          \
    return (size_t)&GetTypeId;                  \
}

class ClassA
{
public:
    GET_TYPE_ID
};

class ClassB
{
public:
    GET_TYPE_ID
};
Alexander
  • 959
  • 5
  • 11
  • 29
0

There is nothing standardized. Further, there's no hack that I've found that's foolproof.

Best I've been able to come up with:

template < class DERIVED, int sid >
class Foo
{
    public:    
      static int s_id()    
      {        
          return sid;
      }    
};

Foo<MyClass, 123456>   derivedObject;
James Curran
  • 101,701
  • 37
  • 181
  • 258
0

You can do the following:

#include <iostream>


template <int id = 5>
class blah
{
public:
    static const int cid = id; 
};

int main(int argc, char *argv[])
{
    std::cout << blah<>::cid << " " << blah<10>::cid << std::endl;

}

I don't know if it's a good idea though. The blah<> part is a bit unintuitive too. Maybe you're better off assigning them ids manually, or you can create a base type, give it a template argument with your class id.

Skurmedel
  • 21,515
  • 5
  • 53
  • 66