Casi todos los recursos de C ++ que he visto que discuten este tipo de cosas me dicen que debería preferir enfoques polimórficos al uso de RTTI (identificación de tipo en tiempo de ejecución). En general, me tomo este tipo de consejo en serio y trataré de comprender la lógica; después de todo, C ++ es una bestia poderosa y difícil de entender en toda su profundidad. Sin embargo, para esta pregunta en particular, me estoy quedando en blanco y me gustaría ver qué tipo de consejos puede ofrecer Internet. Primero, permítanme resumir lo que he aprendido hasta ahora, enumerando las razones comunes que se citan por las que RTTI se "considera perjudicial":
Algunos compiladores no lo usan / RTTI no siempre está habilitado
Realmente no compro este argumento. Es como decir que no debería usar las funciones de C ++ 14, porque hay compiladores que no lo admiten. Y, sin embargo, nadie me disuadiría de usar las funciones de C ++ 14. La mayoría de los proyectos tendrán influencia sobre el compilador que están usando y cómo está configurado. Incluso citando la página de manual de gcc:
-fno-rtti
Deshabilite la generación de información sobre cada clase con funciones virtuales para su uso por las características de identificación de tipo en tiempo de ejecución de C ++ (dynamic_cast y typeid). Si no usa esas partes del idioma, puede ahorrar algo de espacio usando esta bandera. Tenga en cuenta que el manejo de excepciones utiliza la misma información, pero G ++ la genera según sea necesario. El operador dynamic_cast todavía se puede usar para conversiones que no requieren información de tipo en tiempo de ejecución, es decir, conversiones a "void *" o a clases base inequívocas.
Lo que esto me dice es que si no estoy usando RTTI, puedo desactivarlo. Eso es como decir, si no está usando Boost, no tiene que vincularlo. No tengo que planificar el caso en el que alguien esté compilando -fno-rtti
. Además, el compilador fallará alto y claro en este caso.
Cuesta memoria extra / Puede ser lento
Siempre que me siento tentado a usar RTTI, eso significa que necesito acceder a algún tipo de información o rasgo de mi clase. Si implemento una solución que no usa RTTI, esto generalmente significa que tendré que agregar algunos campos a mis clases para almacenar esta información, por lo que el argumento de la memoria es algo nulo (daré un ejemplo de esto más adelante).
De hecho, un dynamic_cast puede ser lento. Sin embargo, generalmente hay formas de evitar tener que usarlo en situaciones críticas para la velocidad. Y no veo la alternativa. Esta respuesta SO sugiere usar una enumeración, definida en la clase base, para almacenar el tipo. Eso solo funciona si conoce todas sus clases derivadas a priori. ¡Eso es un gran "si"!
A partir de esa respuesta, también parece que el costo de RTTI tampoco está claro. Diferentes personas miden diferentes cosas.
Los elegantes diseños polimórficos harán que RTTI sea innecesario
Este es el tipo de consejo que me tomo en serio. En este caso, simplemente no puedo encontrar buenas soluciones que no sean RTTI que cubran mi caso de uso de RTTI. Déjame darte un ejemplo:
Digamos que estoy escribiendo una biblioteca para manejar gráficos de algún tipo de objetos. Quiero permitir que los usuarios generen sus propios tipos cuando usan mi biblioteca (por lo que el método enum no está disponible). Tengo una clase base para mi nodo:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Ahora, mis nodos pueden ser de diferentes tipos. Que tal esto:
class red_node : virtual public node_base
{
public:
red_node();
virtual ~red_node();
void get_redness();
};
class yellow_node : virtual public node_base
{
public:
yellow_node();
virtual ~yellow_node();
void set_yellowness(int);
};
Demonios, ¿por qué ni siquiera uno de estos?
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
La última función es interesante. He aquí una forma de escribirlo:
void orange_node::poke_adjacent_oranges()
{
auto adj_nodes = get_adjacent_nodes();
foreach(auto node, adj_nodes) {
// In this case, typeid() and static_cast might be faster
std::shared_ptr<orange_node> o_node = dynamic_cast<orange_node>(node);
if (o_node) {
o_node->poke();
}
}
}
Todo esto parece claro y limpio. No tengo que definir atributos o métodos donde no los necesito, la clase de nodo base puede permanecer delgada y mezquina. Sin RTTI, ¿por dónde empiezo? Tal vez pueda agregar un atributo node_type a la clase base:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
private:
std::string my_type;
};
¿Es std :: string una buena idea para un tipo? Quizás no, pero ¿qué más puedo usar? ¿Inventa un número y espera que nadie más lo esté usando todavía? Además, en el caso de mi orange_node, ¿qué pasa si quiero usar los métodos de red_node y yellow_node? ¿Tendría que almacenar varios tipos por nodo? Eso parece complicado.
Conclusión
Estos ejemplos no parecen demasiado complejos o inusuales (estoy trabajando en algo similar en mi trabajo diario, donde los nodos representan el hardware real que se controla a través del software y que hacen cosas muy diferentes dependiendo de lo que sean). Sin embargo, no conocería una forma limpia de hacer esto con plantillas u otros métodos. Tenga en cuenta que estoy tratando de comprender el problema, no defender mi ejemplo. Mi lectura de páginas como la respuesta SO que vinculé anteriormente y esta página en Wikilibros parece sugerir que estoy haciendo un mal uso de RTTI, pero me gustaría saber por qué.
Entonces, volviendo a mi pregunta original: ¿Por qué es preferible el 'polimorfismo puro' al uso de RTTI?
fuente
node_base
es parte de una biblioteca y los usuarios crearán sus propios tipos de nodos. Entonces no pueden modificarnode_base
para permitir otra solución, por lo que quizás RTTI se convierta en su mejor opción en ese momento. Por otro lado, hay otras formas de diseñar una biblioteca de este tipo para que los nuevos tipos de nodos puedan encajar de forma mucho más elegante sin necesidad de utilizar RTTI (y otras formas de diseñar los nuevos tipos de nodos también).Respuestas:
Una interfaz describe lo que uno necesita saber para interactuar en una situación dada en el código. Una vez que amplía la interfaz con "la jerarquía de tipo entero", la interfaz de "superficie" se pone muy grande, lo que hace razonar sobre ello más difícil .
Como ejemplo, su "empujar naranjas adyacentes" significa que yo, como tercero, ¡no puedo emular ser una naranja! Usted declaró de forma privada un tipo naranja, luego usó RTTI para hacer que su código se comporte de manera especial al interactuar con ese tipo. Si quiero "ser naranja", debo estar dentro de su jardín privado.
Ahora todos los que se emparejan con "orangeness" se emparejan con todo su tipo de naranja, e implícitamente con todo su jardín privado, en lugar de con una interfaz definida.
Si bien a primera vista parece una excelente manera de extender la interfaz limitada sin tener que cambiar todos los clientes (agregando
am_I_orange
), lo que tiende a suceder es que osifica la base del código y evita una mayor extensión. La naranja especial se vuelve inherente al funcionamiento del sistema y le impide crear un reemplazo "mandarina" para naranja que se implementa de manera diferente y tal vez elimine una dependencia o resuelva algún otro problema con elegancia.Esto significa que su interfaz debe ser suficiente para resolver su problema. Desde esa perspectiva, ¿por qué solo necesita pinchar naranjas y, de ser así, por qué no estaba disponible la naranja en la interfaz? Si necesita un conjunto difuso de etiquetas que se pueden agregar ad-hoc, puede agregarlo a su tipo:
Esto proporciona una ampliación masiva similar de su interfaz desde una especificación estricta hasta una amplia basada en etiquetas. Excepto que en lugar de hacerlo a través de RTTI y los detalles de implementación (también conocido como "¿cómo se implementa? ¿Con el tipo naranja? Ok, pasa"), lo hace con algo fácilmente emulado a través de una implementación completamente diferente.
Esto incluso se puede extender a métodos dinámicos, si lo necesita. "¿Apoyas ser engañado con argumentos de Baz, Tom y Alice? Ok, engañarte". En un sentido amplio, esto es menos intrusivo que un elenco dinámico para llegar al hecho de que el otro objeto es del tipo que conoces.
Ahora los objetos mandarina pueden tener la etiqueta naranja y seguir el juego, mientras están desacoplados de implementación.
Todavía puede conducir a un gran lío, pero al menos es un lío de mensajes y datos, no jerarquías de implementación.
La abstracción es un juego de disociar y ocultar irrelevancia. Hace que el código sea más fácil de razonar localmente. RTTI está abriendo un agujero directamente desde la abstracción hasta los detalles de implementación. Esto puede facilitar la resolución de un problema, pero tiene el costo de bloquearlo en una implementación específica con mucha facilidad.
fuente
dynamic_cast
(RTTI), en cuyo caso las etiquetas son redundantes. La segunda posibilidad, una clase de Dios, es abominable. En resumen, esta respuesta tiene muchas palabras que creo que suenan bien para los programadores de Java, pero el contenido real no tiene sentido.La mayor parte de la persuasión moral contra este o aquel rasgo es la tipicidad que se origina a partir de la observación de que hay una serie de usos erróneos de ese rasgo.
Donde fallan los moralistas es que presumen que TODOS los usos están mal concebidos, mientras que de hecho las características existen por una razón.
Ellos tienen lo que yo solía llamar el "fontanero compleja": piensan todos los grifos están funcionando mal debido a que todos los grifos que están llamados a reparación son. La realidad es que la mayoría de los grifos funcionan bien: ¡simplemente no llama a un plomero para ellos!
Algo loco que puede suceder es cuando, para evitar el uso de una función determinada, los programadores escriben una gran cantidad de código repetitivo en realidad, reimplementando de forma privada exactamente esa función. (¿Alguna vez conociste clases que no usan RTTI ni llamadas virtuales, pero tienen un valor para rastrear qué tipo derivado real son? Eso no es más que una reinvención de RTTI disfrazada).
Hay una manera general de pensar polimorfismo:
IF(selection) CALL(something) WITH(parameters)
. (Lo siento, pero la programación, al ignorar la abstracción, se trata de eso)El uso de polimorfismo en tiempo de diseño (conceptos) en tiempo de compilación (basado en plantilla-deducción), tiempo de ejecución (herencia y basado en funciones virtuales) o basado en datos (RTTI y conmutación), depende de la cantidad de decisiones que se conozcan. en cada una de las etapas de la producción y cuán variables son en cada contexto.
La idea es que:
cuanto más pueda anticipar, mayor será la posibilidad de detectar errores y evitar errores que afecten al usuario final.
Si todo es constante (incluidos los datos), puede hacer todo con la metaprogramación de plantillas. Después de que se realizó la compilación en constantes actualizadas, todo el programa se reduce a solo una declaración de retorno que escupe el resultado .
Si hay varios casos que se conocen todos en tiempo de compilación , pero no conoce los datos reales sobre los que deben actuar, entonces el polimorfismo en tiempo de compilación (principalmente CRTP o similar) puede ser una solución.
Si la selección de los casos depende de los datos (no de los valores conocidos en tiempo de compilación) y la conmutación es unidimensional (lo que se puede hacer se puede reducir a un solo valor), entonces el envío basado en funciones virtuales (o en general "tablas de punteros de función ") es necesario.
Si el cambio es multidimensional, dado que no existe un despacho de tiempo de ejecución múltiple nativo en C ++, entonces tiene que:
Si no solo se conoce el cambio, sino incluso las acciones, no se conoce el tiempo de compilación, entonces se requieren secuencias de comandos y análisis : los datos en sí deben describir la acción que se tomará en ellos.
Ahora, dado que cada uno de los casos que enumeré puede verse como un caso particular de lo que le sigue, puede resolver cada problema abusando de la solución más baja también para problemas asequibles con la más alta.
Eso es lo que la moralización realmente empuja a evitar. ¡Pero eso no significa que los problemas que viven en los dominios más bajos no existan!
Golpear RTTI solo para golpearlo, es como golpear
goto
solo para golpearlo. Cosas para loros, no programadores.fuente
Se ve un poco ordenado en un pequeño ejemplo, pero en la vida real pronto terminarás con un conjunto largo de tipos que pueden empujarse entre sí, algunos de ellos quizás solo en una dirección.
¿Qué pasa
dark_orange_node
, oblack_and_orange_striped_node
, odotted_node
? ¿Puede tener puntos de diferentes colores? ¿Qué pasa si la mayoría de los puntos son naranjas, entonces se pueden pinchar?Y cada vez que tenga que agregar una nueva regla, tendrá que volver a visitar todas las
poke_adjacent
funciones y agregar más declaraciones if.Como siempre, es difícil crear ejemplos genéricos, te lo daré.
Pero si tuviera que hacer este ejemplo específico , agregaría un
poke()
miembro a todas las clases y dejaría que algunos de ellos ignoren la llamada (void poke() {}
) si no están interesados.Seguramente eso sería incluso menos costoso que comparar los
typeid
s.fuente
supportsBehaviour
yinvokeBehaviour
cada clase puede tener una lista de comportamientos. Un comportamiento sería Poke y podría ser agregado a la lista de comportamientos admitidos por todas las clases que quieran ser pinchables.Creo que ha entendido mal esos argumentos.
Hay una serie de lugares de codificación C ++ donde no se debe utilizar RTTI. Donde se utilizan conmutadores de compilador para desactivar RTTI a la fuerza. Si está codificando dentro de ese paradigma ... es casi seguro que ya se le ha informado de esta restricción.
Por tanto, el problema está en las bibliotecas . Es decir, si está escribiendo una biblioteca que depende de RTTI, los usuarios que desactivan RTTI no pueden usar su biblioteca. Si desea que esas personas usen su biblioteca, entonces no puede usar RTTI, incluso si su biblioteca también es utilizada por personas que pueden usar RTTI. Lo que es igualmente importante, si no puede usar RTTI, tiene que buscar un poco más para las bibliotecas, ya que el uso de RTTI es un factor decisivo para usted.
Hay muchas cosas que no se pueden hacer en bucles en caliente. No asignas memoria. No vas iterando a través de listas enlazadas. Etcétera. RTTI ciertamente puede ser otra de esas cosas de "no hagas esto aquí".
Sin embargo, considere todos sus ejemplos de RTTI. En todos los casos, tiene uno o más objetos de tipo indeterminado y desea realizar alguna operación en ellos que puede no ser posible para algunos de ellos.
Eso es algo con lo que tienes que trabajar a nivel de diseño . Puede escribir contenedores que no asignen memoria y que encajen en el paradigma "STL". Puede evitar las estructuras de datos de listas vinculadas o limitar su uso. Puede reorganizar matrices de estructuras en estructuras de matrices o lo que sea. Cambia algunas cosas, pero puede mantenerlo compartimentado.
¿Cambiar una operación RTTI compleja en una llamada de función virtual regular? Eso es un problema de diseño. Si tiene que cambiar eso, entonces es algo que requiere cambios en cada clase derivada. Cambia la forma en que la cantidad de código interactúa con varias clases. El alcance de tal cambio se extiende mucho más allá de las secciones de código críticas para el rendimiento.
Entonces ... ¿por qué lo escribiste de manera incorrecta para empezar?
¿A que final?
Dices que la clase base es "magra y mezquina". Pero realmente ... es inexistente . En realidad no hace nada .
Basta con mirar a tu ejemplo:
node_base
. ¿Qué es? Parece ser una cosa que tiene otras cosas adyacentes. Esta es una interfaz de Java (Java pre-genérico en eso): una clase que existe únicamente para ser algo que los usuarios pueden transmitir a la realidad tipo . Tal vez agregue alguna característica básica como adyacencia (agrega JavaToString
), pero eso es todo.Hay una diferencia entre "delgado y mezquino" y "transparente".
Como dijo Yakk, tales estilos de programación se limitan a la interoperabilidad, porque si toda la funcionalidad está en una clase derivada, los usuarios fuera de ese sistema, sin acceso a esa clase derivada, no pueden interoperar con el sistema. No pueden anular funciones virtuales y agregar nuevos comportamientos. Ni siquiera pueden llamar esas funciones.
Pero lo que también hacen es hacer que sea un gran dolor hacer cosas nuevas, incluso dentro del sistema. Considere su
poke_adjacent_oranges
función. ¿Qué sucede si alguien quiere unlime_node
tipo que se pueda pinchar comoorange_node
s? Bueno, no podemos derivarlime_node
deorange_node
; eso no tiene sentido.En su lugar, tenemos que agregar un nuevo
lime_node
derivado denode_base
. Luego cambie el nombre depoke_adjacent_oranges
apoke_adjacent_pokables
. Y luego, intente transmitir aorange_node
ylime_node
; el elenco que funcione es el que pinchamos.Sin embargo,
lime_node
necesita su propiopoke_adjacent_pokables
. Y esta función necesita realizar las mismas comprobaciones de lanzamiento.Y si agregamos un tercer tipo, no solo tenemos que agregar su propia función, sino que debemos cambiar las funciones en las otras dos clases.
Obviamente, ahora haces
poke_adjacent_pokables
una función gratuita, para que funcione para todos. Pero, ¿qué supone que sucede si alguien agrega un cuarto tipo y se olvida de agregarlo a esa función?Hola, quiebra silenciosa . El programa parece funcionar más o menos bien, pero no lo es. Si hubiera
poke
sido una función virtual real , el compilador habría fallado cuando no anuló la función virtual pura denode_base
.A su manera, no tiene tales comprobaciones de compilador. Oh, claro, el compilador no buscará virtuales no puros, pero al menos tiene protección en los casos en que la protección es posible (es decir, no hay una operación predeterminada).
El uso de clases base transparentes con RTTI conduce a una pesadilla de mantenimiento. De hecho, la mayoría de los usos de RTTI provocan dolores de cabeza por mantenimiento. Eso no significa que RTTI no sea útil (es vital para que
boost::any
funcione, por ejemplo). Pero es una herramienta muy especializada para necesidades muy especializadas.De esa manera, es "dañino" de la misma manera que
goto
. Es una herramienta útil que no debería descartarse. Pero su uso debería ser poco común dentro de su código.Entonces, si no puede usar clases base transparentes y casting dinámico, ¿cómo puede evitar las interfaces pesadas? ¿Cómo evita que se propaguen todas las funciones a las que podría querer llamar en un tipo para que no se propaguen a la clase base?
La respuesta depende de para qué sirve la clase base.
Las clases base transparentes como
node_base
simplemente están usando la herramienta incorrecta para el problema. Las listas vinculadas se manejan mejor con plantillas. El tipo de nodo y la adyacencia los proporcionaría un tipo de plantilla. Si desea poner un tipo polimórfico en la lista, puede hacerlo. Solo useBaseClass*
comoT
en el argumento de la plantilla. O su puntero inteligente preferido.Pero hay otros escenarios. Uno es un tipo que hace muchas cosas, pero tiene algunas partes opcionales. Una instancia en particular podría implementar ciertas funciones, mientras que otra no lo haría. Sin embargo, el diseño de estos tipos suele ofrecer una respuesta adecuada.
La clase "entidad" es un ejemplo perfecto de esto. Esta clase lleva mucho tiempo plagando a los desarrolladores de juegos. Conceptualmente, tiene una interfaz gigantesca, que vive en la intersección de casi una docena de sistemas completamente dispares. Y diferentes entidades tienen diferentes propiedades. Algunas entidades no tienen ninguna representación visual, por lo que sus funciones de representación no hacen nada. Y todo esto se determina en tiempo de ejecución.
La solución moderna para esto es un sistema de estilo de componentes.
Entity
es simplemente un contenedor de un conjunto de componentes, con algo de pegamento entre ellos. Algunos componentes son opcionales; una entidad que no tiene representación visual no tiene el componente "gráficos". Una entidad sin IA no tiene un componente "controlador". Etcétera.Las entidades en un sistema de este tipo son solo indicadores de componentes, y la mayor parte de su interfaz se proporciona al acceder a los componentes directamente.
El desarrollo de un sistema de componentes de este tipo requiere reconocer, en la etapa de diseño, que ciertas funciones están agrupadas conceptualmente, de modo que todos los tipos que implementan una las implementarán todas. Esto le permite extraer la clase de la posible clase base y convertirla en un componente separado.
Esto también ayuda a seguir el principio de responsabilidad única. Una clase de componentes de este tipo solo tiene la responsabilidad de ser titular de componentes.
De Matthew Walton:
Bien, exploremos eso.
Para que esto tenga sentido, lo que tendría que tener es una situación en la que alguna biblioteca L proporcione un contenedor u otro contenedor estructurado de datos. El usuario puede agregar datos a este contenedor, iterar sobre su contenido, etc. Sin embargo, la biblioteca no hace nada con estos datos; simplemente gestiona su existencia.
Pero ni siquiera gestiona tanto su existencia como su destrucción . La razón es que, si se espera que use RTTI para tales propósitos, entonces está creando clases que L ignora. Esto significa que su código asigna el objeto y lo entrega a L para su administración.
Ahora bien, hay casos en los que algo como esto es un diseño legítimo. Señalización de eventos / paso de mensajes, colas de trabajo seguras para subprocesos, etc. El patrón general aquí es el siguiente: alguien está realizando un servicio entre dos piezas de código que es apropiado para cualquier tipo, pero el servicio no necesita ser consciente de los tipos específicos involucrados .
En C, este patrón está escrito
void*
y su uso requiere mucho cuidado para evitar que se rompa. En C ++, este patrón está escritostd::experimental::any
(pronto se escribirástd::any
).La forma en que esto debería funcionar es que L proporciona una
node_base
clase que toma unany
que representa sus datos reales. Cuando recibe el mensaje, el elemento de trabajo de la cola de subprocesos o lo que sea que esté haciendo, loany
envía al tipo apropiado, que tanto el remitente como el receptor conocen.Así que en lugar de derivar
orange_node
denode_data
, simplemente se pega unorange
interior denode_data
'sany
campo de miembro. El usuario final lo extrae y lo usaany_cast
para convertirlo aorange
. Si el elenco falla, entonces no lo fueorange
.Ahora, si usted está en todo familiarizado con la implementación de
any
, lo más probable es decir, "Hey, espera un minuto:any
internamente usa RTTI para hacerany_cast
. Trabajo" A lo que respondo, "... sí".Ese es el punto de una abstracción . En el fondo de los detalles, alguien está usando RTTI. Pero al nivel en el que debería estar operando, RTTI directo no es algo que debería estar haciendo.
Debería utilizar tipos que le proporcionen la funcionalidad que desea. Después de todo, realmente no quieres RTTI. Lo que desea es una estructura de datos que pueda almacenar un valor de un tipo determinado, ocultarlo de todos excepto del destino deseado y luego volver a convertirlo a ese tipo, con la verificación de que el valor almacenado realmente es de ese tipo.
Eso se llama
any
. Se utiliza RTTI, pero el usoany
es muy superior a la utilización de RTTI directamente, ya que se ajusta más correctamente la semántica deseados.fuente
Si llama a una función, como regla, realmente no le importa qué pasos precisos tomará, solo que algún objetivo de nivel superior se logre dentro de ciertas restricciones (y cómo la función hace que eso suceda es realmente su propio problema).
Cuando usa RTTI para hacer una preselección de objetos especiales que pueden hacer un determinado trabajo, mientras que otros en el mismo conjunto no pueden, está rompiendo esa cómoda visión del mundo. De repente, se supone que la persona que llama sabe quién puede hacer qué, en lugar de simplemente decirle a sus secuaces que sigan adelante. A algunas personas les molesta esto, y sospecho que esta es una gran parte de la razón por la que RTTI se considera un poco sucio.
¿Hay algún problema de rendimiento? Tal vez, pero nunca lo he experimentado, y podría ser sabiduría de hace veinte años, o de personas que creen honestamente que usar tres instrucciones de ensamblaje en lugar de dos es una hinchazón inaceptable.
Entonces, cómo lidiar con esto ... Dependiendo de su situación, podría tener sentido tener propiedades específicas de nodo agrupadas en objetos separados (es decir, toda la API 'naranja' podría ser un objeto separado). El objeto raíz podría tener una función virtual para devolver la API 'naranja', devolviendo nullptr por defecto para los objetos que no son de color naranja.
Si bien esto puede ser excesivo dependiendo de su situación, le permitiría consultar en el nivel raíz si un nodo específico admite una API específica y, si lo hace, ejecutar funciones específicas para esa API.
fuente
C ++ se basa en la idea de la verificación de tipos estáticos.
[1] RTTI, es decir,
dynamic_cast
ytype_id
es verificación de tipo dinámica.Entonces, esencialmente se pregunta por qué la verificación de tipo estática es preferible a la verificación de tipo dinámica. Y la respuesta simple es, si la verificación de tipo estática es preferible a la verificación de tipo dinámica, depende . En mucho. Pero C ++ es uno de los lenguajes de programación que están diseñados en torno a la idea de la verificación de tipos estáticos. Y esto significa que, por ejemplo, el proceso de desarrollo, en particular las pruebas, normalmente se adapta a la verificación de tipos estáticos y luego se ajusta mejor.
Re
puede hacer este proceso-nodos-heterogéneos-de-un-gráfico con verificación de tipo estático y sin conversión de ningún tipo a través del patrón de visitante, por ejemplo, así:
Esto supone que se conoce el conjunto de tipos de nodos.
Cuando no lo es, el patrón de visitantes (hay muchas variaciones) se puede expresar con unos pocos lanzamientos centralizados, o solo uno.
Para un enfoque basado en plantillas, consulte la biblioteca Boost Graph. Es triste decir que no estoy familiarizado con él, no lo he usado. Así que no estoy seguro exactamente de qué hace y cómo, y en qué grado usa la verificación de tipo estático en lugar de RTTI, pero dado que Boost generalmente se basa en plantillas con la verificación de tipo estático como la idea central, creo que encontrará que su sub-biblioteca Graph también se basa en la verificación de tipos estáticos.
[1] Información del tipo de tiempo de ejecución .
fuente
Por supuesto, hay un escenario en el que el polimorfismo no puede ayudar: los nombres.
typeid
le permite acceder al nombre del tipo, aunque la forma en que se codifica este nombre está definida por la implementación. Pero generalmente esto no es un problema, ya que puede comparar dostypeid
-s:Lo mismo se aplica a los hash.
dañina es, sin duda una exageración: RTTI tiene algunos inconvenientes, pero lo hace tener ventajas también.
Realmente no tiene que usar RTTI. RTTI es una herramienta para resolver problemas de programación orientada a objetos: si utiliza otro paradigma, estos probablemente desaparecerán. C no tiene RTTI, pero aún funciona. En cambio, C ++ es totalmente compatible con OOP y le brinda múltiples herramientas para superar algunos problemas que pueden requerir información de tiempo de ejecución: una de ellas es RTTI, que aunque tiene un precio. Si no puede pagarlo, lo que es mejor que declare solo después de un análisis de rendimiento seguro, todavía existe la vieja escuela
void*
: es gratis. Gratuito. Pero no obtienes seguridad de tipo. Así que se trata de intercambios.Si escribe (posiblemente estrictamente) código C ++ conforme, puede esperar el mismo comportamiento independientemente de la implementación. Las implementaciones que cumplen con los estándares deben admitir las características estándar de C ++.
Pero tenga en cuenta que en algunos entornos C ++ define (los «independientes»), no es necesario proporcionar RTTI y tampoco las excepciones,
virtual
etc. RTTI necesita una capa subyacente para funcionar correctamente que se ocupa de los detalles de bajo nivel, como el ABI y la información de tipo real.Estoy de acuerdo con Yakk con respecto a RTTI en este caso. Sí, podría usarse; pero ¿es lógicamente correcto? El hecho de que el idioma le permita omitir esta verificación no significa que deba hacerlo.
fuente