Tutorial :Unique class type Id that is safe and holds across library boundaries



Question:

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!


Solution:1

This can be done with very little code:

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


Solution:2

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.


Solution:3

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.


Solution:4

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.


Solution:5

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;  }  





        
Previous
Next Post »