Уникальный идентификатор типа класса, который безопасен и хранит границы библиотек
Я был бы признателен за любую помощь, поскольку С++ не является моим основным языком.
У меня есть класс шаблонов, который получен в нескольких библиотеках. Я пытаюсь выяснить способ однозначного присвоения id int каждому производному классу. Я должен уметь это делать из статического метода, т.е.
template < class DERIVED >
class Foo
{
public:
static int s_id()
{
// return id unique for DERIVED
}
// ...
};
Thank you!
Ответы
Ответ 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());
}