Quase todos os recursos C ++ que vi que discutem esse tipo de coisa me dizem que eu deveria preferir abordagens polimórficas ao usar RTTI (identificação de tipo em tempo de execução). Em geral, levo esse tipo de conselho a sério e tentarei entender a lógica - afinal, C ++ é uma besta poderosa e difícil de entender em toda a sua profundidade. No entanto, para esta questão em particular, estou em branco e gostaria de ver que tipo de conselho a Internet pode oferecer. Em primeiro lugar, deixe-me resumir o que aprendi até agora, listando os motivos comuns citados pelos quais o RTTI é "considerado prejudicial":
Alguns compiladores não o usam / RTTI nem sempre está habilitado
Eu realmente não aceito esse argumento. É como dizer que não devo usar os recursos do C ++ 14, porque existem compiladores por aí que não os suportam. E ainda, ninguém me desencorajaria de usar os recursos do C ++ 14. A maioria dos projetos terá influência sobre o compilador que estão usando e como ele está configurado. Mesmo citando a página de manual do gcc:
-fno-rtti
Desative a geração de informações sobre cada classe com funções virtuais para uso pelos recursos de identificação de tipo em tempo de execução C ++ (dynamic_cast e typeid). Se você não usa essas partes da linguagem, pode economizar espaço usando este sinalizador. Observe que o tratamento de exceções usa as mesmas informações, mas o G ++ as gera conforme necessário. O operador dynamic_cast ainda pode ser usado para conversões que não requerem informações de tipo de tempo de execução, ou seja, conversões para "void *" ou para classes de base não ambíguas.
O que isso me diz é que, se não estiver usando RTTI, posso desativá-lo. É como dizer que, se você não estiver usando o Boost, não será necessário criar um link para ele. Não tenho que planejar o caso em que alguém está compilando com -fno-rtti
. Além disso, o compilador falhará em alto e bom som neste caso.
Custa memória extra / pode ser lento
Sempre que fico tentado a usar RTTI, isso significa que preciso acessar algum tipo de informação ou característica de minha classe. Se eu implementar uma solução que não use RTTI, isso geralmente significa que terei que adicionar alguns campos às minhas classes para armazenar essas informações, então o argumento de memória é meio vazio (darei um exemplo disso mais adiante).
Um dynamic_cast pode ser lento, de fato. No entanto, geralmente há maneiras de evitar ter de usá-lo em situações críticas de velocidade. E não vejo bem a alternativa. Essa resposta do SO sugere o uso de um enum, definido na classe base, para armazenar o tipo. Isso só funciona se você conhecer todas as suas classes derivadas a priori. É um grande "se"!
A partir dessa resposta, parece também que o custo do RTTI também não está claro. Pessoas diferentes medem coisas diferentes.
Desenhos polimórficos elegantes tornarão o RTTI desnecessário
Esse é o tipo de conselho que levo a sério. Nesse caso, simplesmente não consigo encontrar boas soluções não RTTI que cubram meu caso de uso RTTI. Deixe-me dar um exemplo:
Digamos que estou escrevendo uma biblioteca para lidar com gráficos de algum tipo de objeto. Quero permitir que os usuários gerem seus próprios tipos ao usar minha biblioteca (portanto, o método enum não está disponível). Eu tenho uma classe base para meu nó:
class node_base
{
public:
node_base();
virtual ~node_base();
std::vector< std::shared_ptr<node_base> > get_adjacent_nodes();
};
Agora, meus nós podem ser de tipos diferentes. Que tal estes:
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);
};
Inferno, por que nem mesmo um destes:
class orange_node : public red_node, public yellow_node
{
public:
orange_node();
virtual ~orange_node();
void poke();
void poke_adjacent_oranges();
};
A última função é interessante. Esta é uma maneira de escrever:
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();
}
}
}
Tudo isso parece claro e limpo. Não preciso definir atributos ou métodos onde não preciso deles, a classe de nó base pode permanecer enxuta e média. Sem RTTI, por onde eu começo? Talvez eu possa adicionar um atributo node_type à classe 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;
};
Std :: string é uma boa ideia para um tipo? Talvez não, mas o que mais posso usar? Invente um número e espera que ninguém mais o esteja usando ainda? Além disso, no caso do meu orange_node, e se eu quiser usar os métodos de red_node e yellow_node? Eu teria que armazenar vários tipos por nó? Isso parece complicado.
Conclusão
Esses exemplos não parecem muito complexos ou incomuns (estou trabalhando em algo semelhante em meu trabalho diário, onde os nós representam o hardware real que é controlado pelo software e que faz coisas muito diferentes dependendo do que são). No entanto, eu não saberia uma maneira limpa de fazer isso com modelos ou outros métodos. Observe que estou tentando entender o problema, não defender meu exemplo. Minha leitura de páginas como a resposta SO que criei no link acima e esta página no Wikilivros parecem sugerir que estou usando RTTI incorretamente, mas gostaria de saber por quê.
Então, de volta à minha pergunta original: por que o 'polimorfismo puro' é preferível ao uso de RTTI?
node_base
seja parte de uma biblioteca e que os usuários farão seus próprios tipos de nó. Então, eles não podem modificar node_base
para permitir outra solução, então talvez RTTI se torne sua melhor opção. Por outro lado, existem outras maneiras de projetar essa biblioteca de modo que novos tipos de nós possam se encaixar com muito mais elegância, sem a necessidade de usar RTTI (e outras maneiras de projetar os novos tipos de nós também).