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

前端 未结 11 655
离开以前
离开以前 2020-12-06 05:52

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 uniq

相关标签:
11条回答
  • 2020-12-06 06:04

    This can be done with very little code:

    template < class DERIVED >
    class Foo
    {
    public:
        static int s_id()
        {
            return reinterpret_cast<int>(&s_id);
        }
    };
    
    0 讨论(0)
  • 2020-12-06 06:04

    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.

    0 讨论(0)
  • 2020-12-06 06:04

    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;
    
    0 讨论(0)
  • 2020-12-06 06:08

    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)

    0 讨论(0)
  • 2020-12-06 06:10

    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.

    0 讨论(0)
  • 2020-12-06 06:13
    #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());
    }
    
    0 讨论(0)
提交回复
热议问题