tipos - try on c++
Evitando dynamic_cast/RTTI (6)
Hace poco estuve trabajando en un código de C ++ para un proyecto paralelo (la biblioteca de cpp-markdown
, para los curiosos), y me encontré con una pregunta de codificación sobre la que quisiera obtener algunas opiniones.
cpp-markdown
tiene una clase base llamada Token
, que tiene varias subclases. Dos de las subclases principales son Container
(que contiene colecciones de otros Token
s) y TextHolder
(utilizado como una clase base para Token
que contienen texto, por supuesto).
La mayor parte del procesamiento se maneja a través de funciones virtuales, pero algunas de ellas se manejan mejor en una sola función. Para eso, terminé usando dynamic_cast
para lanzar el puntero de un Token*
a una de sus subclases, por lo que pude llamar a funciones que son específicas de la subclase y sus clases secundarias. No hay posibilidad de que la conversión falle, porque el código es capaz de decir cuándo se necesita algo a través de funciones virtuales (como isUnmatchedOpenMarker
).
Hay otras dos maneras en que puedo ver para manejar esto:
Cree todas las funciones que quiero llamar como funciones virtuales de
Token
, y simplemente déjelas con un cuerpo vacío para cada subclase, excepto las que deben manejarlas, o ...Cree una función virtual en
Token
que devuelva el puntero correctamente tipeado athis
cuando se invoque a ciertos subtipos, y un puntero nulo si se invoca a cualquier otra cosa. Básicamente es una extensión del sistema de función virtual que ya estoy usando allí.
El segundo método parece mejor que el existente y el primero, para mí. Pero me gustaría conocer otras opiniones experimentadas de los desarrolladores de C ++. O si me estoy preocupando demasiado por trivialidades. :-)
# 1 contamina el espacio de nombres de clase y vtable para los objetos que no lo necesitan. Ok cuando tienes un puñado de métodos que generalmente se implementarán, pero que son feos cuando solo se necesitan para una sola clase derivada.
# 2 es simplemente dynamic_cast<>
en un vestido de lunares y lápiz labial. No simplifica el código del cliente y enreda toda la jerarquía, requiriendo que la base y cada clase derivada sean semi-conscientes de cualquier otra clase derivada.
Solo usa dynamic_cast<>
. Para eso está ahí.
¿Por qué quieres evitar el uso de dynamic_cast
? ¿Está causando un cuello de botella inaceptable en su aplicación? Si no, quizás no valga la pena hacer nada con el código en este momento.
Si está de acuerdo con intercambiar cierta cantidad de seguridad por un poco de velocidad en su situación particular, debería estar bien haciendo un static_cast
; sin embargo, esto está consolidando su suposición de que usted conoce el tipo de objeto y no hay posibilidad de que el elenco sea malo. Si su suposición llega a ser incorrecta más tarde, podría terminar con algunos errores misteriosos en su código. Volviendo a mi pregunta original, ¿está realmente seguro de que la compensación vale la pena aquí?
En cuanto a las opciones que enumeró: la primera realmente no suena como una solución tanto como un truco de último minuto que esperaría ver cuando alguien escribía código a las 3 AM. La funcionalidad que se escurre hacia la base de la jerarquía de clases es uno de los antipatrones más comunes que afectan a las personas nuevas en OOP. No lo hagas
Para la segunda opción que dynamic_cast
, cualquier opción como esta simplemente está reimplementando dynamic_cast
- si estás en una plataforma con solo compiladores de mierda disponibles (he escuchado historias sobre el compilador de Gamecube tomando un cuarto de la RAM disponible del sistema con información RTTI) esto podría valer la pena, pero es más que probable que solo estés perdiendo el tiempo. ¿De verdad estás seguro de que vale la pena preocuparte por esto?
Cosas divertidas. El dynamic_cast
en el tokenizer implica que quieres dividir el Token en algo que crea el Token basado en la posición actual en el flujo de texto y la lógica en el Token. Cada token será independiente o tendrá que crear un token como el anterior para procesar el texto correctamente.
De esta forma, extrae los elementos genéricos y aún puede analizar en función de la jerarquía de tokens. Incluso puede hacer que todo este analizador sea controlado por datos sin usar dynamic_cast
.
La verdadera forma limpia de evitar dynamic_cast es tener punteros del tipo correcto en el lugar correcto. La abstracción debería encargarse del resto.
En mi humilde opinión, la razón por la cual dynamic_cast tiene esta reputación es porque su desempeño se degrada un poco cada vez que agrega otro subtipo en su jerarquía de clases. Si tiene de 4 a 5 clases en la jerarquía, no hay nada de qué preocuparse.
Si quieres ser inteligente, también puedes construir un patrón de despacho doble , que es dos tercios del patrón de visitante .
- Cree una clase
TokenVisitor
base que contenga métodos devisit(SpecificToken*)
virtual vacíavisit(SpecificToken*)
. - Agregue un único método de
accept(TokenVisitor*)
virtualaccept(TokenVisitor*)
al Token que llama al método tipeado correctamente en TokenVisitor pasado. - Derive de TokenVisitor para las diversas cosas que tendrá que hacer de diversas maneras sobre todos los tokens.
Para el patrón de visitante completo, útil para las estructuras de árbol, token->accept(this);
métodos de accept
predeterminados token->accept(this);
sobre los niños que llaman a token->accept(this);
en cada.
Si sabe que la conversión no puede ser inválida, simplemente use static_cast.