Подтвердить что ты не робот

Уникальный идентификатор типа класса, который безопасен и хранит границы библиотек

Я был бы признателен за любую помощь, поскольку С++ не является моим основным языком.

У меня есть класс шаблонов, который получен в нескольких библиотеках. Я пытаюсь выяснить способ однозначного присвоения id int каждому производному классу. Я должен уметь это делать из статического метода, т.е.


template < class DERIVED >
class Foo
{
public:
    static int s_id()
    {
        // return id unique for DERIVED
    }
    // ...
};
Thank you!
4b9b3361

Ответ 1

Вот что я в итоге сделал. Если у вас есть отзывы (плюсы, минусы), пожалуйста, дайте мне знать.


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

По сути, идентификатор будет назначен после сравнения строк, а не сравнения указателей. Это не идеальное с точки зрения скорости, но я сделал идентификатор static const, поэтому ему нужно будет вычислять только один раз для каждого DERIVED.

Ответ 2

Это можно сделать с очень маленьким кодом:

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

Ответ 3

В современном С++ (03 - если вы используете последний компилятор, такой как gcc), вы можете использовать typeid ключевое слово, чтобы получить объект type_info, который предоставляет информацию о базовом типе, по крайней мере во время выполнения, - стандартную (а затем кросс-платформенную) функцию.

Я взял пример из википедии и добавил проверку шаблона/наследования, кажется, работает хорошо, но я не уверен в версии int (это хак, использующий предположение, что компилятор будет иметь имена типов где-то в простое пространство для чтения... это может быть неправильное предположение).

Идентификатор строки кажется намного лучше для межплатформенной идентификации, если вы можете использовать его в вашем случае. Он не совместим с кросс-компилятором, так как имя, которое оно дает вам, является "реализацией, определенной" стандартом, как это предлагается в комментариях.

Код полного тестового приложения:

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

Выходы:

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

Это работает, по крайней мере, на VC10Beta1 и VC9, должно работать на GCC. Кстати, для использования typeid (и dynamic_cast) вам нужно разрешить информацию типа runtime для вашего компилятора. Он должен быть включен по умолчанию. На некоторых таблицах/компиляторах (я думаю о некоторых встроенных аппаратных средствах) RTTI не включается, потому что у него есть стоимость, поэтому в некоторых крайних случаях вам нужно найти лучшее решение.

Ответ 4

В моей предыдущей компании мы сделали это, создав макрос, который примет имя класса в качестве параметра, создаст локальный статический объект с уникальным идентификатором (на основе имени класса), а затем создаст переопределение виртуальной функции, объявленной в базовый класс, который возвращал статический член. Таким образом, вы можете получить идентификатор во время выполнения из любого экземпляра иерархии объектов, аналогично методу getClass() в объекте java, но гораздо более примитивному.

Ответ 5

Нет ничего стандартизованного. Кроме того, нет никакого взлома, который я обнаружил, что надежный.

Лучше всего мне удалось придумать:

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

Foo<MyClass, 123456>   derivedObject;

Ответ 6

Какой идентификатор? Вы ищете атомарно увеличивающуюся int? Если строка в порядке, как насчет:

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

Если он должен быть int, но не автоматически увеличивается, вы можете хэшировать, что для 128-битного целого вряд ли будут иметь коллизии (хотя, вероятно, большее количество, чем вам нужно)

Ответ 7

Я не на 100% доволен ответами до сих пор, и я разработал у меня одно решение. Идея состоит в том, чтобы вычислить хэш имени типа с помощью typeinfo. Все происходит один раз при загрузке приложения, поэтому нет перегрузки во время выполнения. Это решение будет работать также с использованием разделяемых библиотек, поскольку имя типа и хэш его будут согласованы.

Это код, который я использую. Это отлично работает для меня.

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

Ответ 8

Приведенный ниже фрагмент работает в версиях VS (2015) и Release:

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

Также испытано и протестировано на GCC v7.3 (Ubuntu 16.04) и LLVM v10.0.0 (Mac OS High Sierra).

Как это работает: каждый экземпляр шаблона TypeId<> получает свой уникальный экземпляр sDummy со своим собственным уникальным адресом. Если честно, я не совсем уверен, почему функционально-статическая версия не работала в релизе - я подозреваю, что идентичные сворачивания и оптимизации comdat.

Упражнение для читателя: по крайней мере, типы const и ref должны иметь тот же идентификатор типа, что и необработанный тип.

Ответ 9

Вы можете сделать следующее:

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

}

Я не знаю, если это хорошая идея. Часть blah<> тоже немного неинтуитивна. Возможно, вам лучше назначить их ids вручную или создать базовый тип, дать ему аргумент шаблона с вашим идентификатором класса.

Ответ 10

#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());
}