c++ - std:: cualquiera sin RTTI, ¿cómo funciona?
stl c++17 (3)
La implementación manual de un RTTI limitado no es tan difícil. Vas a necesitar funciones genéricas estáticas. Eso lo puedo decir sin proporcionar una implementación completa. Aquí hay una posibilidad:
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;};
};
Esta es mi primera observación; Lejos de ser ideal, le faltan muchos detalles. Uno puede usar una instancia de meta
como miembro de datos no estáticos de su supuesta implementación de std::any
.
Si quiero usar std::any
, puedo usarlo con RTTI apagado. El siguiente ejemplo compila y se ejecuta como se esperaba también con -fno-rtti
con gcc.
int main()
{
std::any x;
x=9.9;
std::cout << std::any_cast<double>(x) << std::endl;
}
Pero, ¿cómo std::any
almacena la información de tipo? Como veo, si llamo a std::any_cast
con el tipo "incorrecto" std::bad_any_cast
excepción std::bad_any_cast
como se esperaba.
¿Cómo se realiza eso o es esto tal vez solo una característica de gcc?
Encontré que boost::any
no necesitaba RTTI, pero tampoco encontré cómo se resuelve. ¿Aumenta :: alguna necesidad RTTI? .
Cavar en el encabezado STL en sí mismo no me da respuesta. Ese código es casi ilegible para mí.
Una de las posibles soluciones es generar una identificación única para cada tipo posiblemente almacenado en any
(supongo que ya sabe cómo funciona internamente). El código que puede hacerlo puede verse algo como esto:
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;
}
};
};
Con esto implementado, puede usar el id del tipo en lugar de RTTI typeinfo
para verificar rápidamente el tipo.
Observe el uso de variables estáticas dentro de funciones y funciones estáticas. Esto se hace para evitar el problema del orden indefinido de inicialización de variables estáticas.
TL; DR; std::any
mantiene un puntero a una función miembro estática de una clase con plantilla. Esta función puede realizar muchas operaciones y es específica para un tipo dado, ya que la instancia real de la función depende de los argumentos de plantilla de la clase.
La implementación de std::any
en libstdc ++ no es tan compleja, puedes echarle un vistazo:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/any
Básicamente, std::any
tiene dos cosas:
- Un puntero a un almacenamiento asignado (dinámicamente);
- Un puntero a una "función de administrador de almacenamiento":
void (*_M_manager)(_Op, const any*, _Arg*);
Cuando construyes o asignas un nuevo std::any
con un objeto de tipo T
, _M_manager
apunta a una función específica del tipo T
(que en realidad es una función miembro estática de la clase específica de 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) { /* ... */ }
Como esta función es específica de un tipo dado, no necesita RTTI para realizar las operaciones requeridas por std::any
.
Además, es fácil comprobar que estás lanzando al tipo correcto dentro de std::any_cast
. Aquí está el núcleo de la implementación de gcc de 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;
}
Puede ver que es simplemente una verificación de igualdad entre la función almacenada dentro del objeto que está tratando de emitir ( _any->_M_manager
) y la función de administrador del tipo que desea convertir ( &any::_Manager<decay_t<_Tp>>::_S_manage
).
La clase _Manager<_Tp>
es en realidad un alias para _Manager_internal<_Tp>
o _Manager_external<_Tp>
dependiendo de _Tp
. Esta clase también se usa para la asignación / construcción de objetos para la clase std::any
.