c++ polymorphism rtti typeinfo

c++ - ¿Por qué es std:: type_info polimórfico?



polymorphism rtti (4)

3 / Deja abierta la posibilidad de metaclases con tipos personalizados: cada class A polimórfica real class A obtendría una "metaclase" derivada A__type_info , que se deriva de type_info . Quizás esas clases derivadas podrían exponer a los miembros que llaman a la new A con varios argumentos de constructor de una manera segura para el tipo, y cosas como esas. Pero hacer que type_info polimórfico en sí mismo hace que una idea de este tipo sea básicamente imposible de implementar, porque tendrías que tener metaclases para tus metaclases, hasta el infinito, lo cual es un problema si todos los objetos type_info tienen una duración de almacenamiento estática. Tal vez salvo que esta sea la razón para hacerla polimórfica.

Inteligente...

De todos modos, no estoy de acuerdo con este razonamiento: tal implementación podría descartar fácilmente las type_info para los tipos derivados de type_info , incluido el propio type_info .

¿Hay alguna razón por la std::type_info se especifique que std::type_info sea ​​polimórfico? El destructor está especificado para ser virtual (y hay un comentario al efecto de "para que sea polimórfico" en El diseño y la evolución de C ++). Realmente no puedo ver una razón convincente de por qué. No tengo ningún caso de uso específico, solo me preguntaba si alguna vez hubo una razón o una historia detrás de esto.

Aquí hay algunas ideas que he encontrado y rechazado:

  1. Es un punto de extensibilidad: las implementaciones podrían definir subclases, y los programas podrían tratar de dynamic_cast a std::type_info a otro tipo derivado, definido por la implementación. Este es posiblemente el motivo, pero parece que para las implementaciones es tan fácil agregar un miembro definido por la implementación, que posiblemente podría ser virtual. Los programas que deseen probar estas extensiones serían necesariamente no portátiles de todos modos.
  2. Es para garantizar que los tipos derivados se destruyan correctamente al delete un puntero base. Pero no hay tipos derivados estándar, los usuarios no pueden definir tipos derivados útiles, porque type_info no tiene constructores públicos estándar, por lo que delete un puntero type_info nunca es legal ni portátil. Y los tipos derivados no son útiles porque no pueden construirse, el único uso que conozco para tales tipos derivados no construibles es en la implementación de cosas como el rasgo de tipo is_polymorphic .
  3. Deja abierta la posibilidad de metaclases con tipos personalizados: cada class A polimórfica real class A obtendría una "metaclase" derivada A__type_info , que se deriva de type_info . Quizás esas clases derivadas podrían exponer a los miembros que llaman a la new A con varios argumentos de constructor de una manera segura para el tipo, y cosas como esas. Pero hacer que type_info polimórfico en sí mismo hace que una idea de este tipo sea básicamente imposible de implementar, porque tendrías que tener metaclases para tus metaclases, hasta el infinito, lo cual es un problema si todos los objetos type_info tienen una duración de almacenamiento estática. Tal vez salvo que esta sea la razón para hacerla polimórfica.
  4. Hay algo de uso para aplicar las características de RTTI (aparte de dynamic_cast ) a std::type_info , o alguien pensó que era lindo, o vergonzoso si type_info no era polimórfico. Pero dado que no hay un tipo derivado estándar, y no hay otras clases en la jerarquía estándar a las que se pueda intentar de manera cruzada, la pregunta es: ¿qué? ¿Hay un uso para expresiones como typeid(std::type_info) == typeid(typeid(A)) ?
  5. Es porque los implementadores crearán su propio tipo derivado privado (como creo que GCC hace). Pero, ¿para qué molestarse en especificarlo? Incluso si el destructor no se especificara como virtual y un implementador decidiera que debería serlo, seguramente la implementación podría declararlo virtual, porque no cambia el conjunto de operaciones permitidas en type_info , por lo que un programa portátil no podría para decir la diferencia
  6. Es algo que tiene que ver con compiladores con ABIs parcialmente compatibles que coexisten, posiblemente como resultado de un enlace dinámico. Quizás los implementadores podrían reconocer su propia subclase type_info (en lugar de una originada por otro proveedor) de una manera portátil si se garantiza que type_info sea ​​virtual.

El último es el más plausible para mí en este momento, pero es bastante débil.


Acerca de la identificación "global" más simple que puede tener en C ++ es un nombre de clase, y typeinfo proporciona una manera de comparar tales identificaciones para la igualdad. Pero el diseño es tan incómodo y limitado que luego necesita envolver typeinfo en alguna clase contenedora, por ejemplo, para poder poner instancias en colecciones. Andrei Alexandrescu hizo eso en su "Modern C ++ Design" y creo que ese contenedor de información de tipo es parte de la biblioteca Loki; Probablemente hay uno también en Boost; y es bastante fácil rodar el tuyo, por ejemplo, ver mi propia envoltura .

Pero incluso para una envoltura de este tipo, en general no hay necesidad de un destructor virtual en typeinfo .

Por lo tanto, la pregunta no es tanto "huh, ¿por qué hay un destructor virtual" sino más bien, como lo veo, "eh, por qué el diseño es tan atrasado, incómodo y no utilizable directamente"? Y lo atribuyo al proceso de estandarización. Por ejemplo, los iostreams tampoco son exactamente ejemplos de un diseño magnífico; No es algo para emular.


El estándar de C ++ dice que typeid devuelve un objeto de tipo type_info, O UNA subclase DEFINIDA POR LA IMPLEMENTACIÓN del mismo. Entonces ... supongo que esta es la respuesta. Así que no veo por qué rechazas tus puntos 1 y 2.

El párrafo 5.2.8, cláusula 1 de la norma C ++ actual dice:

El resultado de una expresión typeid es un lvalue de tipo estático const std :: type_info (18.5.1) y tipo dinámico const std :: type_info o nombre de const donde nombre es una clase definida por la implementación derivada de std :: type_info que conserva el comportamiento descrito en 18.5.1.61) La vida útil del objeto referido por el valor l se extiende hasta el final del programa. No se especifica si se llama al destructor para el objeto type_info al final del programa.

Lo que a su vez significa que uno podría escribir el siguiente código es legal y const type_info& x = typeid(expr); : const type_info& x = typeid(expr); lo que puede requerir que type_info sea polimórfico


Supongo que está ahí para la conveniencia de los implementadores. Les permite definir clases extendidas de type_info , y eliminarlas a través de los punteros a type_info al salir del programa, sin tener que construir un compilador especial para llamar al destructor correcto, o de lo contrario saltar a través de aros.

seguramente la implementación podría declararlo virtual, porque no cambia el conjunto de operaciones permitidas en type_info, por lo que un programa portátil no podría notar la diferencia.

No creo que eso sea verdad. Considera lo siguiente:

#include <typeinfo> struct A { int x; }; struct B { int x; }; int main() { const A *a1 = dynamic_cast<const A*>(&typeid(int)); B b; const A *a2 = dynamic_cast<const A*>(&b); }

Ya sea que sea razonable o no, el primer lanzamiento dinámico está permitido (y se evalúa como un puntero nulo), mientras que el segundo lanzamiento dinámico no está permitido. Por lo tanto, si type_info se definió en el estándar para tener el destructor no virtual predeterminado, pero una implementación agregó un destructor virtual, entonces un programa portátil podría indicar la diferencia [*].

Me parece más simple poner el destructor virtual en el estándar, que:

a) ponga una nota en el estándar que, si bien la definición de clase implica que type_info no tiene funciones virtuales, está permitido tener un destructor virtual.

b) determinar el conjunto de programas que pueden distinguir si type_info es polimórfico o no, y prohibirlos a todos. Puede que no sean programas muy útiles o productivos, no lo sé, pero para prohibirlos tiene que encontrar un lenguaje estándar que describa la excepción específica que está haciendo a las reglas normales.

Por lo tanto, creo que el estándar tiene que mandar al destructor virtual o prohibirlo. Hacerlo opcional es demasiado complejo (o tal vez debería decir, creo que sería juzgado innecesariamente complejo. La complejidad nunca detuvo al comité de estándares en áreas donde se consideraba que valía la pena ...)

Sin embargo, si estaba prohibido, entonces una implementación podría:

  • agregar un destructor virtual a alguna clase derivada de type_info
  • deriva todos sus objetos typeinfo de esa clase
  • usar eso internamente como la clase base polimórfica para todo

eso resolvería la situación que describí en la parte superior de la publicación, pero el tipo estático de una expresión de tipo typeid aún sería const std::type_info , por lo que sería difícil para las implementaciones definir extensiones donde los programas pueden dynamic_cast a varios destinos para ver qué tipo de objeto type_info tienen en un caso particular. Quizás el estándar esperaba permitir eso, aunque una implementación siempre podría ofrecer una variante de typeid con un tipo estático diferente, o garantizar que un static_cast a una cierta clase de extensión funcionará, y luego dejar que el programa dynamic_cast desde allí.

En resumen, que yo sepa, el destructor virtual es potencialmente útil para los implementadores, y eliminarlo no le otorga a nadie más que eso, no perderíamos tiempo preguntándonos por qué está allí ;-)

[*] En realidad, no he demostrado eso. He demostrado que un programa ilegal, si todo lo demás es igual, se compila. Pero tal vez una implementación podría trabajar alrededor de eso asegurando que todo no sea igual y que no se compile. El is_polymorphic de Boost no es portátil, por lo que si bien es posible que un programa pruebe que una clase es polimórfica, debería ser, puede que no haya forma de que un programa conforme pruebe que una clase no es polimórfica, eso no debería ser . Aunque creo que incluso si es imposible, demostrar que, para eliminar una línea del estándar, es un gran esfuerzo.