std::any без RTTI, как это работает?
Если я хочу использовать std::any
Я могу использовать его с выключенным RTTI. Следующий пример компилируется и запускается, как ожидается, также с -fno-rtti
с gcc.
int main()
{
std::any x;
x=9.9;
std::cout << std::any_cast<double>(x) << std::endl;
}
Но как std::any
хранит информацию о типе? Как я вижу, если я позвоню std::any_cast
с "неправильным" типом я получил std::bad_any_cast
исключение, как и ожидалось.
Как это реализовано или это может быть только функция GCC?
я нашел это boost::any
тоже не нужен RTTI, но я нашел также не то, как это решается. Нужно ли повысить:: любой RTTI?,
Копание в самом заголовке STL не дает мне ответа. Этот код почти не читается для меня.
3 ответа
TL;DR; std::any
содержит указатель на статическую функцию-член шаблонного класса. Эта функция может выполнять много операций и специфична для данного типа, поскольку фактический экземпляр функции зависит от аргументов шаблона класса.
Реализация std::any
в libstdC++ это не так сложно, вы можете посмотреть на это:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any
В принципе, std::any
содержит две вещи:
- Указатель на (динамически) выделенное хранилище;
- Указатель на "функцию менеджера хранилища":
void (*_M_manager)(_Op, const any*, _Arg*);
Когда вы создаете или назначаете новый std::any
с объектом типа T
, _M_manager
указывает на функцию, специфичную для типа T
(которая на самом деле является статической функцией-членом класса, специфичной для T
):
template <typename _ValueType,
typename _Tp = _Decay<_ValueType>,
typename _Mgr = _Manager<_Tp>, // <-- Class specific to T.
__any_constructible_t<_Tp, _ValueType&&> = true,
enable_if_t<!__is_in_place_type<_Tp>::value, bool> = true>
any(_ValueType&& __value)
: _M_manager(&_Mgr::_S_manage) { /* ... */ }
Так как эта функция специфична для данного типа, вам не нужен RTTI для выполнения операций, требуемых std::any
,
Кроме того, легко проверить, что вы используете правильный тип в std::any_cast
, Вот ядро реализации gcc std::any_cast
:
template<typename _Tp>
void* __any_caster(const any* __any) {
if constexpr (is_copy_constructible_v<decay_t<_Tp>>) {
if (__any->_M_manager == &any::_Manager<decay_t<_Tp>>::_S_manage) {
any::_Arg __arg;
__any->_M_manager(any::_Op_access, __any, &__arg);
return __arg._M_obj;
}
}
return nullptr;
}
Вы можете видеть, что это просто проверка на равенство между хранимой функцией внутри объекта, который вы пытаетесь привести (_any->_M_manager
) и функция менеджера того типа, к которому вы хотите привести (&any::_Manager<decay_t<_Tp>>::_S_manage
).
Класс _Manager<_Tp>
на самом деле псевдоним либо _Manager_internal<_Tp>
или же _Manager_external<_Tp>
в зависимости от _Tp
, Этот класс также используется для выделения / строительства объекта для std::any
учебный класс.
Ручная реализация ограниченного RTTI не так сложна. Тебе понадобятся статические универсальные функции. Это я могу сказать без полной реализации. вот одна возможность:
class meta{
static auto id(){
static std::atomic<std::size_t> nextid{};
return ++nextid;//globally unique
};
std::size_t mid=0;//per instance type id
public:
template<typename T>
meta(T&&){
static const std::size_t tid{id()};//classwide unique
mid=tid;
};
meta(meta const&)=default;
meta(meta&&)=default;
meta():mid{}{};
template<typename T>
auto is_a(T&& obj){return mid==meta{obj}.mid;};
};
Это мое первое наблюдение; далеко от идеала, упустив много деталей. Можно использовать один экземпляр meta
в качестве не статического члена данных его предполагаемой реализации std::any
,
Одним из возможных решений является создание уникального идентификатора для каждого типа, который может храниться в any
(Я полагаю, вы знаете, как больше any
внутренне работает). Код, который может это сделать, может выглядеть примерно так:
struct id_gen{
static int &i(){
static int i = 0;
return i;
}
template<class T>
struct gen{
static int id() {
static int id = i()++;
return id;
}
};
};
Благодаря этому вы можете использовать идентификатор типа вместо RTTI typeinfo
быстро проверить тип.
Обратите внимание на использование статических переменных внутри функций и статических функций. Это сделано, чтобы избежать проблемы неопределенного порядка инициализации статической переменной.