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?
fonte
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 modificarnode_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).Respostas:
Uma interface descreve o que é preciso saber para interagir em uma determinada situação no código. Depois de estender a interface com "toda a sua hierarquia de tipo", a "área de superfície" da interface torna-se enorme, o que torna o raciocínio mais difícil .
Por exemplo, seu "cutucar laranjas adjacentes" significa que eu, como terceiro, não posso emular ser uma laranja! Você declarou privativamente um tipo laranja e, em seguida, usa RTTI para fazer seu código se comportar de maneira especial ao interagir com esse tipo. Se eu quiser "ser laranja", que deve estar dentro de seu jardim privado.
Agora, todo mundo que acopla com a "laranja" se acopla com todo o seu tipo de laranja e, implicitamente, com todo o seu jardim privado, em vez de com uma interface definida.
Embora à primeira vista isso pareça uma ótima maneira de estender a interface limitada sem ter que mudar todos os clientes (adicionar
am_I_orange
), o que tende a acontecer em vez disso é que ossifica a base do código e evita outras extensões. A laranja especial torna-se inerente ao funcionamento do sistema e evita que você crie um substituto "tangerina" para o laranja que é implementado de forma diferente e talvez remova uma dependência ou resolva algum outro problema com elegância.Isso significa que sua interface deve ser suficiente para resolver seu problema. Dessa perspectiva, por que você só precisa cutucar laranjas e, se sim, por que a laranja não estava disponível na interface? Se precisar de algum conjunto difuso de tags que pode ser adicionado ad-hoc, você pode adicioná-lo ao seu tipo:
Isso fornece uma ampliação massiva semelhante de sua interface de estritamente especificada para ampla baseada em tag. Exceto em vez de fazer isso por meio de RTTI e detalhes de implementação (também conhecido como "como você está implementado? Com o tipo laranja? Ok, você passa."), Ele faz isso com algo facilmente emulado por meio de uma implementação completamente diferente.
Isso pode até ser estendido a métodos dinâmicos, se necessário. "Você apóia ser enganado com argumentos Baz, Tom e Alice? Ok, enganando você." Em um sentido amplo, isso é menos intrusivo do que um elenco dinâmico para chegar ao fato de que o outro objeto é um tipo que você conhece.
Agora, os objetos tangerina podem ter a etiqueta laranja e jogar junto, enquanto são desacoplados da implementação.
Isso ainda pode levar a uma grande confusão, mas é pelo menos uma confusão de mensagens e dados, não hierarquias de implementação.
Abstração é um jogo de desacoplamento e ocultação de irrelevâncias. Isso torna o código mais fácil de raciocinar localmente. O RTTI está abrindo um buraco direto da abstração para os detalhes de implementação. Isso pode tornar a solução de um problema mais fácil, mas tem o custo de prendê-lo em uma implementação específica com muita facilidade.
fonte
dynamic_cast
(RTTI), caso em que as tags são redundantes. A segunda possibilidade, uma classe de Deus, é abominável. Resumindo, essa resposta tem muitas palavras que acho que soam bem para os programadores Java, mas o conteúdo real não tem sentido.A maior parte da persuasão moral contra este ou aquele traço é tipicamente originada da observação de que há uma série de usos mal concebidos desse traço.
Onde os moralistas falham é que presumem que TODOS os usos são mal concebidos, enquanto na verdade os recursos existem por uma razão.
Eles têm o que eu costumava chamar de "complexo do encanador": acham que todas as torneiras estão funcionando mal porque todas as torneiras que foram chamados para consertar estão. A realidade é que a maioria das torneiras funcionam bem: você simplesmente não chama um encanador para elas!
Uma coisa maluca que pode acontecer é quando, para evitar o uso de um determinado recurso, os programadores escrevem muito código clichê, na verdade, reimplementando de forma privada exatamente esse recurso. (Você já conheceu classes que não usam RTTI nem chamadas virtuais, mas têm um valor para rastrear qual tipo derivado real são? Isso não é mais do que reinvenção RTTI disfarçada.)
Há uma maneira geral a pensar sobre o polimorfismo:
IF(selection) CALL(something) WITH(parameters)
. (Desculpe, mas a programação, ao desconsiderar a abstração, é tudo sobre isso)O uso de polimorfismo de tempo de design (conceitos) em tempo de compilação (baseado em dedução de modelo), tempo de execução (herança e função virtual) ou orientado a dados (RTTI e comutação), depende de quanto das decisões são conhecidas em cada uma das etapas da produção e quão variáveis são em cada contexto.
A ideia é que:
quanto mais você antecipar, melhor será a chance de detectar erros e evitar bugs que afetem o usuário final.
Se tudo for constante (incluindo os dados), você pode fazer tudo com meta-programação de modelo. Depois que a compilação ocorreu em constantes atualizadas, o programa inteiro se reduz a apenas uma instrução de retorno que expõe o resultado .
Se houver vários casos que são conhecidos em tempo de compilação , mas você não sabe sobre os dados reais sobre os quais eles devem agir, então o polimorfismo em tempo de compilação (principalmente CRTP ou similar) pode ser uma solução.
Se a seleção dos casos depende dos dados (não de valores conhecidos em tempo de compilação) e a comutação é monodimensional (o que fazer pode ser reduzido a apenas um valor), então o despacho baseado em função virtual (ou em geral "tabelas de ponteiros de função ") é preciso.
Se a comutação for multidimensional, uma vez que não existe despacho de tempo de execução múltiplo nativo em C ++, você deve:
Se não apenas a troca, mas até mesmo as ações não sejam conhecidas em tempo de compilação, então o script e a análise são necessários: os próprios dados devem descrever a ação a ser executada neles.
Agora, como cada um dos casos que enumerei pode ser visto como um caso particular do que se segue, você pode resolver todos os problemas abusando da solução mais inferior também para problemas acessíveis com a mais alta.
Isso é o que a moralização realmente empurra para evitar. Mas isso não significa que os problemas que vivem nos domínios inferiores não existam!
Bashing RTTI apenas para atacá-lo é como
goto
atacar apenas para atacá-lo. Coisas para papagaios, não programadores.fonte
Parece legal em um pequeno exemplo, mas na vida real você logo terminará com um longo conjunto de tipos que podem cutucar uns aos outros, alguns deles talvez apenas em uma direção.
Sobre
dark_orange_node
, oublack_and_orange_striped_node
, oudotted_node
? Ele pode ter pontos de cores diferentes? E se a maioria dos pontos for laranja, ele pode ser cutucado então?E cada vez que você precisar adicionar uma nova regra, terá que revisitar todas as
poke_adjacent
funções e adicionar mais instruções if.Como sempre, é difícil criar exemplos genéricos, vou te dar isso.
Mas se eu fizesse esse exemplo específico , adicionaria um
poke()
membro a todas as classes e deixaria que alguns deles ignorassem a chamada (void poke() {}
) se não estivessem interessados.Certamente isso seria ainda mais barato do que comparar o
typeid
s.fonte
supportsBehaviour
einvokeBehaviour
e cada classe pode ter uma lista de comportamentos. Um comportamento seria Poke e poderia ser adicionado à lista de Behaviors suportados por todas as classes que desejam ser pokáveis.Acredito que você tenha entendido mal esses argumentos.
Existem vários locais de codificação C ++ onde o RTTI não deve ser usado. Onde as opções do compilador são usadas para desabilitar o RTTI à força. Se você está codificando dentro de tal paradigma ... então quase certamente você já foi informado desta restrição.
O problema, portanto, é com as bibliotecas . Ou seja, se você está escrevendo uma biblioteca que depende de RTTI, sua biblioteca não pode ser usada por usuários que desligam o RTTI. Se você deseja que sua biblioteca seja usada por essas pessoas, ela não pode usar RTTI, mesmo que sua biblioteca também seja usada por pessoas que podem usar RTTI. Igualmente importante, se você não pode usar o RTTI, terá que pesquisar um pouco mais as bibliotecas, já que o uso do RTTI é um obstáculo para você.
Há muitas coisas que você não faz em hot loops. Você não aloca memória. Você não fica iterando em listas vinculadas. E assim por diante. O RTTI certamente pode ser mais uma daquelas coisas "não faça isso aqui".
No entanto, considere todos os seus exemplos de RTTI. Em todos os casos, você tem um ou mais objetos de tipo indeterminado e deseja realizar alguma operação neles que pode não ser possível para alguns deles.
Isso é algo que você tem que trabalhar em um nível de design . Você pode escrever contêineres que não alocam memória que se encaixa no paradigma "STL". Você pode evitar estruturas de dados de listas vinculadas ou limitar seu uso. Você pode reorganizar arrays de structs em structs de arrays ou qualquer outra coisa. Ele muda algumas coisas, mas você pode mantê-lo compartimentado.
Mudar uma operação RTTI complexa em uma chamada de função virtual regular? Esse é um problema de design. Se você tiver que mudar isso, então é algo que requer mudanças para todas as classes derivadas. Ele muda como muitos códigos interagem com várias classes. O escopo de tal mudança se estende muito além das seções críticas de desempenho do código.
Então ... por que você escreveu da maneira errada para começar?
Para quê?
Você diz que a classe base é "enxuta e média". Mas realmente ... é inexistente . Na verdade, não faz nada .
Basta olhar para o exemplo:
node_base
. O que é isso? Parece ser uma coisa que tem outras coisas adjacentes. Esta é uma interface Java (Java pré-genérico): uma classe que existe apenas para ser algo que os usuários possam lançar no tipo real . Talvez você adicione algum recurso básico como adjacência (Java adicionaToString
), mas é isso.Há uma diferença entre "enxuto e médio" e "transparente".
Como disse Yakk, esses estilos de programação se limitam à interoperabilidade, porque se toda a funcionalidade estiver em uma classe derivada, os usuários fora desse sistema, sem acesso a essa classe derivada, não podem interoperar com o sistema. Eles não podem substituir funções virtuais e adicionar novos comportamentos. Eles nem conseguem chamar essas funções.
Mas o que eles também fazem é tornar muito difícil fazer coisas novas, mesmo dentro do sistema. Considere sua
poke_adjacent_oranges
função. O que acontece se alguém quiser umlime_node
tipo que possa ser cutucado como oorange_node
s? Bem, não podemos derivarlime_node
deorange_node
; isso não faz sentido.Em vez disso, temos que adicionar um novo
lime_node
derivado denode_base
. Em seguida, altere o nome depoke_adjacent_oranges
parapoke_adjacent_pokables
. E então, tente lançar paraorange_node
elime_node
; qualquer que seja o elenco, é aquele que cutucamos.No entanto,
lime_node
precisa do seu própriopoke_adjacent_pokables
. E essa função precisa fazer as mesmas verificações de fundição.E se adicionarmos um terceiro tipo, temos que não apenas adicionar sua própria função, mas devemos mudar as funções nas outras duas classes.
Obviamente, agora você faz
poke_adjacent_pokables
uma função livre, para que funcione para todos eles. Mas o que você acha que acontece se alguém adiciona um quarto tipo e se esquece de adicioná-lo a essa função?Olá, quebra silenciosa . O programa parece funcionar mais ou menos bem, mas não está. Se
poke
fosse uma função virtual real , o compilador teria falhado se você não substituísse a função virtual pura denode_base
.Do seu jeito, você não tem tais verificações do compilador. Claro, o compilador não verificará se há virtuais não puros, mas pelo menos você tem proteção nos casos em que a proteção é possível (ou seja: não há operação padrão).
O uso de classes base transparentes com RTTI leva a um pesadelo de manutenção. Na verdade, a maioria dos usos do RTTI leva a dores de cabeça de manutenção. Isso não significa que o RTTI não seja útil (é vital para fazer
boost::any
funcionar, por exemplo). Mas é uma ferramenta muito especializada para muito especializada para necessidades especializadas.Dessa forma, é "prejudicial" da mesma forma que
goto
. É uma ferramenta útil que não deve ser descartada. Mas seu uso deve ser raro em seu código.Portanto, se você não pode usar classes base transparentes e conversão dinâmica, como evitar interfaces gordas? Como você evita que todas as funções que você deseja chamar em um tipo borbulhem até a classe base?
A resposta depende da finalidade da classe base.
Classes de base transparentes como
node_base
estão apenas usando a ferramenta errada para o problema. Listas vinculadas são mais bem tratadas por modelos. O tipo de nó e a adjacência seriam fornecidos por um tipo de modelo. Se quiser colocar um tipo polimórfico na lista, você pode. Basta usarBaseClass*
comoT
no argumento do modelo. Ou seu ponteiro inteligente preferido.Mas existem outros cenários. Um é um tipo que faz muitas coisas, mas tem algumas partes opcionais. Uma determinada instância pode implementar certas funções, enquanto outra não. No entanto, o design de tais tipos geralmente oferece uma resposta adequada.
A classe "entidade" é um exemplo perfeito disso. Esta classe há muito atormenta os desenvolvedores de jogos. Conceitualmente, ele tem uma interface gigantesca, vivendo na interseção de quase uma dúzia de sistemas totalmente díspares. E diferentes entidades têm diferentes propriedades. Algumas entidades não têm nenhuma representação visual, portanto, suas funções de renderização não fazem nada. E tudo isso é determinado em tempo de execução.
A solução moderna para isso é um sistema do tipo componente.
Entity
é meramente um contêiner de um conjunto de componentes, com alguma cola entre eles. Alguns componentes são opcionais; uma entidade que não possui representação visual não possui o componente "gráfico". Uma entidade sem IA não possui componente "controlador". E assim por diante.As entidades em tal sistema são apenas ponteiros para componentes, com a maior parte de sua interface sendo fornecida acessando os componentes diretamente.
O desenvolvimento de tal sistema de componentes requer o reconhecimento, no estágio de design, de que certas funções são conceitualmente agrupadas, de modo que todos os tipos que implementam um irão implementá-las todas. Isso permite que você extraia a classe da classe base potencial e a torne um componente separado.
Isso também ajuda a seguir o Princípio da Responsabilidade Única. Essa classe componentizada tem apenas a responsabilidade de ser detentora de componentes.
De Matthew Walton:
OK, vamos explorar isso.
Para que isso faça sentido, o que você precisa é uma situação em que alguma biblioteca L forneça um contêiner ou outro portador estruturado de dados. O usuário pode adicionar dados a este contêiner, iterar seu conteúdo, etc. No entanto, a biblioteca realmente não faz nada com esses dados; ele simplesmente gerencia sua existência.
Mas nem mesmo administra sua existência tanto quanto sua destruição . A razão é que, se espera-se que você use RTTI para tais propósitos, então você está criando classes que L desconhece. Isso significa que seu código aloca o objeto e o entrega a L para gerenciamento.
Agora, há casos em que algo assim é um design legítimo. Sinalização de eventos / passagem de mensagens, filas de trabalho com segurança de thread, etc. O padrão geral aqui é este: alguém está realizando um serviço entre duas partes de código que é apropriado para qualquer tipo, mas o serviço não precisa estar ciente dos tipos específicos envolvidos .
Em C, esse padrão é escrito
void*
e seu uso requer muito cuidado para não ser quebrado. Em C ++, esse padrão é escritostd::experimental::any
(a ser escrito em brevestd::any
).A maneira como isso deve funcionar é que L fornece uma
node_base
classe que recebe umany
que representa seus dados reais. Quando você recebe a mensagem, o item de trabalho da fila de encadeamento ou o que quer que esteja fazendo, você então converteany
para o tipo apropriado, que tanto o remetente quanto o receptor sabem.Então, ao invés de derivar
orange_node
a partirnode_data
, você simplesmente furar umorange
dentro denode_data
'sany
campo de membro. O usuário final extrai e usaany_cast
para convertê-lo emorange
. Se o elenco falhar, então não foiorange
.Agora, se você estiver familiarizado com a implementação do
any
, provavelmente dirá, "ei, espere um minuto: usa RTTIany
internamente para fazer oany_cast
trabalho." Ao que eu respondo, "... sim".Esse é o ponto de uma abstração . No fundo dos detalhes, alguém está usando RTTI. Mas no nível em que você deveria estar operando, o RTTI direto não é algo que você deveria estar fazendo.
Você deve usar tipos que fornecem a funcionalidade desejada. Afinal, você realmente não quer RTTI. O que você deseja é uma estrutura de dados que possa armazenar um valor de um determinado tipo, ocultá-lo de todos, exceto do destino desejado, e então ser convertido de volta para aquele tipo, com a verificação de que o valor armazenado realmente é desse tipo.
Isso é chamado
any
. Ele usa RTTI, mas usarany
é muito superior ao usar RTTI diretamente, pois se ajusta à semântica desejada de forma mais correta.fonte
Se você chamar uma função, como regra, você realmente não se importa com quais etapas precisas ela tomará, apenas que alguma meta de nível superior seja alcançada dentro de certas restrições (e como a função faz isso acontecer é realmente um problema nosso).
Quando você usa o RTTI para fazer uma pré-seleção de objetos especiais que podem fazer um determinado trabalho, enquanto outros no mesmo conjunto não podem, você está quebrando aquela visão confortável do mundo. De repente, o chamador deve saber quem pode fazer o quê, em vez de simplesmente dizer a seus lacaios para continuarem. Algumas pessoas ficam incomodadas com isso, e suspeito que essa seja uma grande parte da razão pela qual o RTTI é considerado um pouco sujo.
Existe um problema de desempenho? Talvez, mas nunca experimentei isso, e pode ser sabedoria de vinte anos atrás, ou de pessoas que honestamente acreditam que usar três instruções de montagem em vez de duas é um inchaço inaceitável.
Então, como lidar com isso ... Dependendo da sua situação, pode fazer sentido ter quaisquer propriedades específicas do nó agrupadas em objetos separados (ou seja, toda a API 'laranja' pode ser um objeto separado). O objeto raiz poderia então ter uma função virtual para retornar a API 'laranja', retornando nullptr por padrão para objetos não laranja.
Embora isso possa ser um exagero dependendo da sua situação, permitiria a você consultar no nível da raiz se um nó específico oferece suporte a uma API específica e, em caso afirmativo, executar funções específicas a essa API.
fonte
C ++ é baseado na ideia de verificação de tipo estático.
[1] RTTI, ou seja,
dynamic_cast
etype_id
, é a verificação de tipo dinâmico.Então, essencialmente, você está perguntando por que a verificação de tipo estática é preferível à verificação de tipo dinâmica. E a resposta simples é: depende se a verificação de tipo estática é preferível à verificação de tipo dinâmica . Em muito. Mas C ++ é uma das linguagens de programação projetadas em torno da ideia de verificação estática de tipo. E isso significa que, por exemplo, o processo de desenvolvimento, em particular o teste, é normalmente adaptado à verificação de tipo estática e, então, se ajusta melhor.
Ré
você pode fazer este processo-heterogêneo-nós-de-um-gráfico com verificação de tipo estático e nenhuma conversão por meio do padrão de visitante, por exemplo:
Isso pressupõe que o conjunto de tipos de nós seja conhecido.
Quando não é, o padrão de visitante (há muitas variações dele) pode ser expresso com alguns lançamentos centralizados ou, apenas um único.
Para uma abordagem baseada em modelo, consulte a biblioteca Boost Graph. É triste dizer que não estou familiarizado com ele, não o usei. Portanto, não tenho certeza do que ele faz e como, e em que grau ele usa verificação de tipo estático em vez de RTTI, mas como o Boost é geralmente baseado em modelo com verificação de tipo estático como a ideia central, acho que você descobrirá que sua sub-biblioteca Graph também é baseada na verificação de tipo estático.
[1] Informações sobre o tipo de tempo de execução .
fonte
Claro que existe um cenário em que o polimorfismo não pode ajudar: nomes.
typeid
permite acessar o nome do tipo, embora a forma como esse nome é codificado seja definida pela implementação. Mas geralmente isso não é um problema, pois você pode comparar doistypeid
-s:O mesmo vale para hashes.
prejudicial é definitivamente exagero: o RTTI tem algumas desvantagens, mas tem têm vantagens também.
Você realmente não precisa usar o RTTI. RTTI é uma ferramenta para resolver problemas de OOP: se você usar outro paradigma, eles provavelmente desaparecerão. C não tem RTTI, mas ainda funciona. Em vez disso, o C ++ oferece suporte total ao OOP e oferece várias ferramentas para superar alguns problemas que podem exigir informações de tempo de execução: uma delas é de fato RTTI, que, no entanto, tem um preço. Se você não pode pagar por isso, coisa que é melhor você afirmar somente após uma análise de desempenho segura, ainda existe a velha escola
void*
: é grátis. Sem custos. Mas você não tem segurança de tipo. Portanto, é tudo uma questão de comércio.Se você escrever (possivelmente estritamente) o código C ++, pode esperar o mesmo comportamento, independentemente da implementação. As implementações compatíveis com o padrão devem suportar os recursos padrão do C ++.
Mas considere que em alguns ambientes C ++ define (os «independentes»), RTTI não precisa ser fornecido e nem exceções,
virtual
e assim por diante. O RTTI precisa de uma camada subjacente para funcionar corretamente que lida com detalhes de baixo nível, como a ABI e as informações de tipo reais.Concordo com Yakk em relação ao RTTI neste caso. Sim, pode ser usado; mas é logicamente correto? O fato de o idioma permitir que você ignore essa verificação não significa que isso deva ser feito.
fonte