Реализация type_id (T) в С++ без регистрации типа или RTTI
Можно ли реализовать type_id (T) в С++, который не требовал бы
регистрация типа вручную или RTTI?
Все решения, которые я видел (в том числе boost:: typeindex), основаны на
специализации и требуют ручной "регистрации" следующим образом:
class A {
public:
BOOST_TYPE_INDEX_REGISTER_CLASS
virtual ~A(){}
};
struct B: public A {
BOOST_TYPE_INDEX_REGISTER_CLASS
};
Но я хочу иметь возможность получить идентификатор типа для любого произвольного типа, включая типы библиотек, которые я не могу переопределить в своем коде.
Ответы
Ответ 1
Как правило, ответ "нет". Вы не можете реализовать идентификатор справедливого типа без RTTI или специализации.
Но есть один очень сильный трюк. Это неочевидно, поэтому он редко встречается в мире С++.
Каждый современный С++-компилятор поддерживает так называемый макрос функции pretty-print,
который позволяет вам получить уникальный идентификатор функции со всем типом
параметры развернуты.
Итак, вы можете использовать что-то вроде кода ниже:
#pragma once
#undef UNIQUE_FUNCTION_ID
#if defined(_MSC_VER)
#define UNIQUE_FUNCTION_ID __FUNCSIG__
#else
#if defined( __GNUG__ )
#define UNIQUE_FUNCTION_ID __PRETTY_FUNCTION__
#endif
#endif
template<typename T>
class TypeId
{
public:
static int typeId()
{
static int s_id = HASH( UNIQUE_FUNCTION_ID );
return s_id;
}
};
Где HASH
может быть любая хорошая хэш-функция, которая вам нравится.
Недостатки:
- Ваш двоичный файл будет загрязнен длинными константами char для каждого типа, который вы используете (но в реальных приложениях накладные расходы не так проблематичны, мы очень интенсивно использовали этот подход без значительного влияния на размер дистрибутива. UPD: это может быть избегать с помощью
constexpr
)
- Получаемые идентификаторы типов не будут переносимыми для компиляторов, версий компилятора и даже разных построений (часто это не проблема).
- Не все компиляторы поддерживают макрос с семантикой (MSVC, g++ и Clang работают как прелесть)
- Лечит
T
и const T&
как разные типы (но он может быть исправлен с дополнительной обработкой перед хэшированием UNIQUE_FUNCTION_ID
)
Преимущества:
- Очень легко реализовать
- Не требует RTTI и поддерживает произвольный тип
- Работает с исполняемыми файлами с DLL/SharedObjects/DyLibs
- Остается стабильным между выполнением программы
Ответ 2
Я обычно использую указатель на функцию. Поскольку каждый экземпляр функции шаблона имеет другой адрес, я получаю свободный хэширующий механизм, реализованный компилятором.
Вот как я это делаю:
using type_id_t = void(*)();
template<typename>
void type_id() {}
Это все! Теперь вы можете использовать его на следующих картах:
std::map<type_id_t, std::string> myMap;
myMap[type_id<int>] = "a int";
myMap[type_id<SomeType>] = "some type";
Ответ 3
Идиома CRTP и система типов C-ish могут помочь в этом случае:
#include<cstddef>
#include<cassert>
struct B {
static std::size_t cnt() noexcept {
static std::size_t val = 0;
return val++;
}
};
template<typename T>
struct I: private B {
static std::size_t type() noexcept {
static std::size_t t = B::cnt();
return t;
}
};
struct T: I<T> { };
struct S: I<S> { };
int main () {
assert(T::type() != S::type());
T t1, t2;
S s;
assert(t1.type() == t2.type());
assert(t1.type() != s.type());
}
Вы также можете использовать макрос:
#define TypedStruct(C) struct C: I<C>
// ...
TypedStruct(T) { };
TypedStruct(S) { };
Как указано в комментариях, если вы не хотите, чтобы это мешало вашим классам, вы можете использовать аналогичный подход, как следует из:
#include<cstddef>
#include<cassert>
struct B {
static std::size_t cnt() noexcept {
static std::size_t val = 0;
return val++;
}
};
template<typename T>
struct Type: private B {
static const std::size_t type;
};
template<typename T>
const std::size_t Type<T>::type = B::cnt();
struct T { };
struct S { };
int main () {
assert(Type<T>::type != Type<S>::type);
}
Как вы можете видеть, S
и T
не подвержены влиянию класса Type
.
Последний может использоваться в любое время, чтобы дать им уникальный идентификатор.