Existem vários problemas com a reflexão em C ++.
É muito trabalhoso acrescentar, e o comitê de C ++ é bastante conservador e não gasta tempo com novos recursos radicais, a menos que tenham certeza de que valerá a pena. (Uma sugestão para adicionar um sistema de módulos semelhante aos assemblies .NET foi feita e, embora eu ache que há um consenso geral de que seria bom ter, não é a principal prioridade no momento e foi adiada até bem depois C ++ 0x. A motivação para esse recurso é se livrar do #include
sistema, mas também permitiria pelo menos alguns metadados).
Você não paga pelo que não usa. Essa é uma das filosofias básicas de design básicas subjacentes ao C ++. Por que meu código carrega metadados se talvez nunca precise dele? Além disso, a adição de metadados pode inibir a otimização do compilador. Por que devo pagar esse custo no meu código se talvez nunca precise desses metadados?
O que nos leva a outro grande ponto: o C ++ oferece muito poucas garantias sobre o código compilado. É permitido ao compilador fazer praticamente o que quiser, desde que a funcionalidade resultante seja o que é esperado. Por exemplo, suas aulas não precisam
estar lá . O compilador pode otimizá-los, incorporar tudo o que eles fazem, e frequentemente faz exatamente isso, porque mesmo o código de modelo simples tende a criar algumas instanciações de modelo. A biblioteca padrão C ++ conta com essa otimização agressiva. Os atuadores só têm desempenho se a sobrecarga de instanciar e destruir o objeto puder ser otimizada.
operator[]
em um vetor é apenas comparável à indexação de matriz bruta no desempenho, porque todo o operador pode ser incorporado e, portanto, removido inteiramente do código compilado. C # e Java oferecem muitas garantias sobre a saída do compilador. Se eu definir uma classe em C #, essa classe existirá no assembly resultante. Mesmo que eu nunca o use. Mesmo que todas as chamadas para suas funções de membro possam ser incorporadas. A classe tem que estar lá, para que a reflexão possa encontrá-la. Parte disso é aliviada pela compilação de C # no bytecode, o que significa que o compilador JIT poderemova definições de classe e funções embutidas, se desejar, mesmo que o compilador C # inicial não possa. No C ++, você tem apenas um compilador e ele precisa gerar código eficiente. Se você tivesse permissão para inspecionar os metadados de um executável C ++, esperaria ver todas as classes definidas, o que significa que o compilador teria que preservar todas as classes definidas, mesmo que não sejam necessárias.
E depois existem modelos. Modelos em C ++ não são nada como genéricos em outros idiomas. Toda instanciação de modelo cria um
novo tipo. std::vector<int>
é uma classe completamente separada de
std::vector<float>
. Isso adiciona muitos tipos diferentes em um programa inteiro. O que nossa reflexão deveria ver? O modelo std::vector
? Mas como pode, já que é uma construção de código-fonte, que não tem significado no tempo de execução? Teria que ver as classes separadas
std::vector<int>
e
std::vector<float>
. E
std::vector<int>::iterator
e
std::vector<float>::iterator
, o mesmo paraconst_iterator
e assim por diante. E depois que você entra na metaprogramação de modelos, você acaba instanciando centenas de modelos, todos os quais são incorporados e removidos novamente pelo compilador. Eles não têm significado, exceto como parte de um metaprograma em tempo de compilação. Todas essas centenas de classes devem ser visíveis à reflexão? Eles precisariam, porque, caso contrário, nossa reflexão seria inútil, se nem sequer garantir que as classes que eu defini realmente estarão lá . E um problema secundário é que a classe de modelo não existe até que seja instanciada. Imagine um programa que use std::vector<int>
. Nosso sistema de reflexão deveria ser capaz de ver std::vector<int>::iterator
? Por um lado, você certamente esperaria isso. É uma classe importante, e é definida em termos de std::vector<int>
, o que fazexiste nos metadados. Por outro lado, se o programa nunca realmente usar esse modelo de classe de iterador, seu tipo nunca será instanciado e, portanto, o compilador não terá gerado a classe em primeiro lugar. E é tarde demais para criá-lo em tempo de execução, pois requer acesso ao código-fonte.
- E, finalmente, a reflexão não é tão vital em C ++ quanto em C #. O motivo é novamente a metaprogramação de modelos. Ele não pode resolver tudo, mas em muitos casos em que você recorreria à reflexão, é possível escrever um metaprograma que faça a mesma coisa em tempo de compilação.
boost::type_traits
é um exemplo simples. Você quer saber sobre o tipo
T
? Verifique o seu type_traits
. Em C #, você teria que pescar após seu tipo usando reflexão. A reflexão ainda seria útil para algumas coisas (o principal uso que eu posso ver, que a metaprogramação não pode substituir facilmente, é para código de serialização gerado automaticamente), mas carregaria alguns custos significativos para o C ++, e isso não é necessário sempre que necessário. está em outros idiomas.
Edit:
Em resposta aos comentários:
cdleary: Sim, os símbolos de depuração fazem algo semelhante, pois armazenam metadados sobre os tipos usados no executável. Mas eles também sofrem com os problemas que descrevi. Se você já tentou depurar uma versão, saberá o que quero dizer. Existem grandes lacunas lógicas nas quais você criou uma classe no código-fonte, que foi embutida no código final. Se você usasse a reflexão para qualquer coisa útil, seria necessário que ela fosse mais confiável e consistente. Assim, os tipos desapareciam e desapareciam quase toda vez que você compila. Você altera um pequeno detalhe, e o compilador decide alterar quais tipos são incorporados e quais não, como resposta. Como você extrai algo útil disso quando você ' nem sequer garantimos que os tipos mais relevantes serão representados nos seus metadados? O tipo que você estava procurando pode ter existido na última compilação, mas agora se foi. E amanhã, alguém verificará uma pequena mudança inocente em uma pequena função inocente, o que tornará o tipo suficientemente grande para que não fique completamente embutido, para que volte sempre. Isso ainda é útil para símbolos de depuração, mas não muito mais do que isso. Eu odiaria tentar gerar código de serialização para uma classe sob esses termos. mas não muito mais que isso. Eu odiaria tentar gerar código de serialização para uma classe sob esses termos. mas não muito mais que isso. Eu odiaria tentar gerar código de serialização para uma classe sob esses termos.
Evan Teran: É claro que esses problemas poderiam ser resolvidos. Mas isso remonta ao meu ponto 1. Daria muito trabalho, e o comitê C ++ tem muitas coisas que consideram mais importantes. O benefício de obter uma reflexão limitada (e seria limitada) em C ++ é realmente grande o suficiente para justificar o foco nisso à custa de outros recursos? Existe realmente um grande benefício em adicionar recursos à linguagem principal que já pode (principalmente) ser feita através de bibliotecas e pré-processadores como os QTs? Talvez, mas a necessidade é muito menos urgente do que se essas bibliotecas não existissem. Porém, para as suas sugestões específicas, acredito que a proibição de modelos tornaria completamente inútil. Você não conseguiria usar a reflexão na biblioteca padrão, por exemplo. Que tipo de reflexão não seriastd::vector
? Os modelos são uma grande parte do C ++. Um recurso que não funciona em modelos é basicamente inútil.
Mas você está certo, alguma forma de reflexão pode ser implementada. Mas seria uma grande mudança no idioma. Como é agora, os tipos são exclusivamente uma construção em tempo de compilação. Eles existem para o benefício do compilador e nada mais. Uma vez que o código foi compilado, não estão sem aulas. Se você se esforçar, poderá argumentar que as funções ainda existem, mas, na verdade, tudo o que há são várias instruções do assembler de salto e muitos push / pop da pilha. Não há muito o que fazer ao adicionar esses metadados.
Mas, como eu disse, há uma proposta de alterações no modelo de compilação, adicionando módulos independentes, armazenando metadados para tipos selecionados, permitindo que outros módulos os referenciem sem ter que mexer com #include
s. É um bom começo e, para ser sincero, estou surpreso que o comitê padrão não tenha lançado a proposta apenas por ser uma mudança muito grande. Então, talvez em 5 a 10 anos? :)
export
evector<bool>
.typeinfo
'sname()
função deve retornar o nome que foi digitado pelo programador e não algo indefinido. E nos dê um stringifier para enumeradores também. Esta é realmente crucial para serialização / desserialização, ajudando na tomada de fábricas etc.A reflexão requer que alguns metadados sobre os tipos sejam armazenados em algum lugar que possa ser consultado. Como o C ++ é compilado no código de máquina nativo e sofre grandes alterações devido à otimização, a visualização de alto nível do aplicativo é praticamente perdida no processo de compilação; consequentemente, não será possível consultá-los em tempo de execução. Java e .NET usam uma representação de nível muito alto no código binário para máquinas virtuais, tornando possível esse nível de reflexão. Em algumas implementações de C ++, no entanto, existe algo chamado RTTI (Run Time Type Information) que pode ser considerado uma versão simplificada da reflexão.
fonte
Todos os idiomas não devem tentar incorporar todos os recursos de qualquer outro idioma.
C ++ é essencialmente um montador de macros muito, muito sofisticado. NÃO é (no sentido tradicional) uma linguagem de alto nível como C #, Java, Objective-C, Smalltalk, etc.
É bom ter ferramentas diferentes para trabalhos diferentes. Se tivermos apenas martelos, tudo parecerá prego, etc. Ter linguagens de script é útil para alguns trabalhos, e linguagens OO reflexivas (Java, Obj-C, C #) são úteis para outra classe de trabalhos, e super linguagens básicas simples e próximas da máquina são úteis para mais uma classe de tarefas (C ++, C, Assembler).
O C ++ faz um trabalho incrível de estender a tecnologia Assembler para níveis incríveis de gerenciamento de complexidade e abstrações para tornar a programação tarefas maiores e mais complexas muito mais possíveis para os seres humanos. Mas não é necessariamente uma linguagem mais adequada para aqueles que estão abordando o problema de uma perspectiva estritamente de alto nível (Lisp, Smalltalk, Java, C #). Se você precisar de um idioma com esses recursos para implementar melhor uma solução para seus problemas, agradeça àqueles que criaram esses idiomas para que todos nós possamos usar!
Mas C ++ é para aqueles que, por qualquer motivo, precisam ter uma forte correlação entre seu código e a operação da máquina subjacente. Seja sua eficiência, drivers de dispositivo de programação ou interação com os serviços de SO de nível inferior ou qualquer outra coisa, o C ++ é mais adequado para essas tarefas.
C #, Java, Objective-C exigem um sistema de tempo de execução muito maior e mais rico para suportar sua execução. Esse tempo de execução deve ser entregue ao sistema em questão - pré-instalado para suportar a operação do seu software. E essa camada deve ser mantida para vários sistemas de destino, personalizados por ALGUM OUTRO IDIOMA para fazê-lo funcionar nessa plataforma. E essa camada intermediária - aquela camada adaptativa entre o sistema operacional host e o seu código - o tempo de execução, é quase sempre escrita em uma linguagem como C ou C ++, onde a eficiência é a número 1, na qual é possível entender previsivelmente a interação exata entre software e hardware. entendido e manipulado para obter o ganho máximo.
Eu amo Smalltalk, Objective-C, e ter um sistema de tempo de execução rico com reflexão, metadados, coleta de lixo, etc. É possível escrever um código incrível para aproveitar essas facilidades! Mas isso é simplesmente uma camada mais alta na pilha, uma camada que deve repousar nas camadas inferiores, e que, por fim, elas devem estar no SO e no hardware. E sempre precisaremos de uma linguagem mais adequada para criar essa camada: C ++ / C / Assembler.
Adendo: o C ++ 11/14 continua expandindo a capacidade do C ++ para oferecer suporte a abstrações e sistemas de nível superior. Threading, sincronização, modelos de memória precisos, definições de máquina abstrata mais precisas estão permitindo que os desenvolvedores de C ++ obtenham muitas das abstrações de alto nível que algumas dessas linguagens de alto nível costumavam ter domínio exclusivo, enquanto continuam fornecendo informações próximas a desempenho metálico e excelente previsibilidade (ou seja, subsistemas de tempo de execução mínimo). Talvez os recursos de reflexão sejam seletivamente ativados em uma revisão futura do C ++, para aqueles que o desejam - ou talvez uma biblioteca forneça esses serviços de tempo de execução (talvez exista um agora ou o início de um impulso).
fonte
Se você realmente deseja entender as decisões de design que envolvem o C ++, encontre uma cópia do Manual de Referência do C ++ anotado por Ellis e Stroustrup. NÃO está atualizado com o padrão mais recente, mas passa pelo padrão original e explica como as coisas funcionam e, com frequência, como elas foram assim.
fonte
A reflexão para as linguagens contidas é sobre quanto do código-fonte o compilador está disposto a deixar no código do objeto para permitir a reflexão e quanto mecanismo de análise está disponível para interpretar essas informações refletidas. A menos que o compilador mantenha todo o código-fonte por perto, a reflexão será limitada em sua capacidade de analisar os fatos disponíveis sobre o código-fonte.
Compilador do C ++ não mantém qualquer coisa ao redor (bem, ignorando RTTI), para que não fique reflexão na língua. (Os compiladores Java e C # mantêm apenas classes, nomes de métodos e tipos de retorno, para que você obtenha um pouco de dados de reflexão, mas não pode inspecionar expressões ou estrutura de programa, o que significa que mesmo nessas linguagens "ativadas para reflexão" as informações que você pode obter são muito escassas e, conseqüentemente, você realmente não pode fazer muita análise).
Mas você pode sair do idioma e obter recursos completos de reflexão. A resposta para outra discussão excedente de pilha na reflexão em C discute isso.
fonte
A reflexão pode ser e foi implementada em c ++ anteriormente.
Não é um recurso nativo do c ++ porque possui um alto custo (memória e velocidade) que não deve ser definido por padrão pelo idioma - o idioma é orientado para "desempenho máximo por padrão".
Como você não deve pagar pelo que não precisa e, como você mesmo diz, é mais necessário nos editores do que em outros aplicativos, ele deve ser implementado apenas onde você precisar, e não "forçado" a todo o código ( você não precisa refletir sobre todos os dados com os quais trabalhará em um editor ou outro aplicativo similar).
fonte
O motivo pelo qual o C ++ não reflete é que isso exigiria que os compiladores adicionassem informações de símbolos aos arquivos de objetos, como os membros de um tipo de classe, informações sobre os membros, sobre as funções e tudo mais. Isso essencialmente tornaria inúteis os arquivos de inclusão, pois as informações enviadas pelas declarações seriam lidas a partir desses arquivos de objeto (módulos então). No C ++, uma definição de tipo pode ocorrer várias vezes em um programa, incluindo os respectivos cabeçalhos (desde que todas essas definições sejam iguais); portanto, é necessário decidir onde colocar as informações sobre esse tipo, assim como nomear um complicação aqui. A otimização agressiva feita por um compilador C ++, que pode otimizar dezenas de instanciações de modelos de classe, é outro ponto forte. É possível, mas como o C ++ é compatível com o C,
fonte
Existem muitos casos de uso da reflexão em C ++ que não podem ser tratados adequadamente usando construções de tempo de compilação, como a metaprogramação de modelos.
O N3340 propõe indicadores avançados como uma maneira de introduzir reflexão em C ++. Entre outras coisas, trata da questão de não pagar por um recurso, a menos que você o use.
fonte
De acordo com Alistair Cockburn, a subtipagem não pode ser garantida em um ambiente reflexivo .
A reflexão é mais relevante para os sistemas de digitação latente. No C ++, você sabe qual o seu tipo e o que pode fazer com ele.
fonte
A reflexão pode ser opcional, como uma diretiva de pré-processador. Algo como
#pragma enable reflection
Dessa forma, podemos ter o melhor dos dois mundos, sem essas bibliotecas de pragma criadas sem reflexão (sem nenhuma sobrecarga, conforme discutido), então seria o desenvolvedor individual se eles queriam velocidade ou facilidade de uso.
fonte
Se C ++ pudesse ter:
const
modificadorconst
modificadorIsso seria suficiente para criar bibliotecas muito fáceis de usar no cerne do processamento de dados sem tipo que é tão prevalecente nos aplicativos atuais da Web e de banco de dados (todos os orms, mecanismos de mensagens, analisadores xml / json, serialização de dados etc.).
Por exemplo, as informações básicas suportadas pela
Q_PROPERTY
macro (parte do Qt Framework) http://qt.nokia.com/doc/4.5/properties.html expandidas para abranger os métodos de classe ee) - seriam extraordinariamente benéficas para C ++ e para a comunidade de software em geral.Certamente, a reflexão a que me refiro não abrangeria o significado semântico ou questões mais complexas (como comentários nos números das linhas de código-fonte, análise de fluxo de dados etc.) - mas também não acho que essas sejam necessárias para fazer parte de um padrão de linguagem.
fonte
alguns bons links sobre reflexão em C ++ que acabei de encontrar:
Documento de trabalho do padrão C ++: aspectos da reflexão em C ++
Um exemplo simples de reflexão usando modelos
fonte
Reflexão em C ++, acredito ser de importância crucial se o C ++ for usado como uma linguagem para acesso ao banco de dados, manipulação de sessões da Web / http e desenvolvimento de GUI. A falta de reflexão impede ORMs (como Hibernate ou LINQ), analisadores XML e JSON que instancinam classes, serialização de dados e muitos outros thigns (onde dados inicialmente sem texto devem ser usados para criar uma instância de uma classe).
Uma opção de tempo de compilação disponível para um desenvolvedor de software durante o processo de compilação pode ser usada para eliminar essa preocupação de 'você paga pelo que usa'.
Como um desenvolvedor de empresa não precisa da reflexão para ler dados de uma porta serial - então não use o switch. Mas, como desenvolvedor de banco de dados que deseja continuar usando C ++, sou constantemente escalado para criar um código horrível e difícil de manter que mapeia dados entre membros de dados e construções de banco de dados.
Nem a serialização Boost nem outro mecanismo estão realmente resolvendo a reflexão - ela deve ser feita pelo compilador - e, uma vez feito, o C ++ será novamente estudado nas escolas e usado em software que lida com processamento de dados
Para mim, esta edição nº 1 (e as primitivas ingênuas de segmentação são a edição nº 2).
fonte
É basicamente porque é um "extra opcional". Muitas pessoas escolhem C ++ em vez de linguagens como Java e C # para que tenham mais controle sobre a saída do compilador, por exemplo, um programa menor e / ou mais rápido.
Se você optar por adicionar reflexão, existem várias soluções disponíveis .
fonte