type_info c++
¿Por qué `std:: make_shared` realiza dos asignaciones separadas con`-fno-rtti`? (3)
#include <memory>
struct foo { };
int main() { std::make_shared<foo>(); }
El ensamblaje generado por g++7
y clang++5
con -fno-exceptions -Ofast
para el código anterior:
Contiene una sola llamada al
operator new
si no se pasa-fno-rtti
.Contiene dos llamadas separadas al
operator new
si se-fno-rtti
.
Esto se puede verificar fácilmente en gcc.godbolt.org ( versión clang++5
) :
¿Por qué está pasando esto? ¿Por qué la desactivación de RTTI evita que make_shared make_shared
el objeto y controle las asignaciones de bloque ?
¿Por qué la desactivación de RTTI evita que make_shared unifique el objeto y controle las asignaciones de bloque?
Se puede ver en el ensamblador (simplemente pegar el texto es realmente preferible tanto para vincular como para tomar fotografías de él) que la versión unificada no asigna un foo
simple sino un std::_Sp_counted_ptr_inplace
, y además ese tipo tiene un vtable (recuerde que necesita un destructor virtual en general, para hacer frente a eliminaciones personalizadas)
mov QWORD PTR [rax], OFFSET FLAT:
vtable for
std::_Sp_counted_ptr_inplace<foo, std::allocator<foo>,
(__gnu_cxx::_Lock_policy)2>+16
Si deshabilita RTTI, no puede generar el puntero contado in situ porque debe ser virtual.
Tenga en cuenta que la versión no in situ aún se refiere a un vtable, pero parece que simplemente está almacenando directamente la dirección del destructor des-virtualizado.
Naturalmente, std::shared_ptr
se implementará asumiendo que el compilador es compatible con rtti
. Pero se puede implementar sin ella. Ver shared_ptr sin RTTI? .
Siguiendo el ejemplo de este viejo error libstdc ++ # 42019 de GCC . Podemos ver que Jonathan Wakely agregó una solución para hacer esto posible sin RTTI.
En libstdc ++ de GCC, std::make_shared
usa los servicios de std::make_shared
que usa un constructor no estándar (como se ve en el código, reproducido a continuación).
Como se ve en este parche, desde la línea 753 , puede ver que obtener el borrado predeterminado simplemente requiere usar los servicios de typeid
si RTTI está habilitado , de lo contrario, requiere una asignación separada que no dependa de RTTI.
EDITAR: 9 - Mayo -2017: eliminado el código con derechos de autor publicado anteriormente aquí
No he investigado libcxx
, pero quiero creer que hicieron algo similar ...
No hay buena razón. Esto parece un problema de QoI en libstdc ++.
Usando clang 4.0, libc ++ no tiene este problema. , mientras libstdc ++ lo hace .
La implementación de libstdc ++ con RTTI se basa en get_deleter
:
void* __p = _M_refcount._M_get_deleter(typeid(__tag));
_M_ptr = static_cast<_Tp*>(__p);
__enable_shared_from_this_helper(_M_refcount, _M_ptr, _M_ptr);
_M_ptr = static_cast<_Tp*>(__p);
y en general, get_deleter
no es posible implementar sin RTTI.
Parece que está utilizando la posición de borrado y la etiqueta para almacenar la T
en esta implementación.
Básicamente, la versión get_deleter
utiliza get_deleter
. get_deleter
basó en RTTI. Conseguir que make_shared
funcione sin RTTI
requería reescribirlo, y tomaron una ruta fácil que hizo que hiciera dos asignaciones.
make_shared
unifica los bloques de conteo T
y de referencia. Supongo que con los borradores de tamaño variable y las T
tamaño variable se vuelven desagradables, por lo que reutilizan el bloque de tamaño variable del almacén para almacenar el T
Un get_deleter
modificado (interno) que no hizo RTTI y devolvió un void*
podría ser suficiente para hacer lo que necesitan de este eliminador; pero posiblemente no.