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 быстро проверить тип.

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

Другие вопросы по тегам