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?
node_base
es parte de una biblioteca y los usuarios crearán sus propios tipos de nodos. Entonces no pueden modificar node_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).