Eu geralmente evito a conversão de tipos tanto quanto possível, pois tenho a impressão de que é uma prática de codificação ruim e pode incorrer em uma penalidade de desempenho.
Mas se alguém me pedisse para explicar exatamente por que isso acontece, eu provavelmente iria olhar para eles como um cervo nos faróis.
Então, por que / quando o elenco é ruim?
É geral para java, c #, c ++ ou cada ambiente de tempo de execução diferente lida com ele em seus próprios termos?
As especificações de qualquer linguagem são bem-vindas, por exemplo, por que é ruim em c ++?
Respostas:
Você marcou isso com três idiomas e as respostas são muito diferentes entre os três. A discussão de C ++ implica mais ou menos na discussão de cast de C também, e isso dá (mais ou menos) uma quarta resposta.
Já que é aquele que você não mencionou explicitamente, começarei com C. Os casts de C têm vários problemas. Uma é que eles podem fazer uma série de coisas diferentes. Em alguns casos, o elenco não faz nada mais do que dizer ao compilador (em essência): "cala a boca, eu sei o que estou fazendo" - ou seja, ele garante que mesmo quando você fizer uma conversão que poderia causar problemas, o compilador não o avisará sobre esses problemas potenciais. Apenas por exemplo
char a=(char)123456;
,. O resultado exato desta implementação definido (depende do tamanho e da assinatura dochar
) e, exceto em situações bastante estranhas, provavelmente não é útil. C casts também variam se são algo que acontece apenas em tempo de compilação (ou seja, você está apenas dizendo ao compilador como interpretar / tratar alguns dados) ou algo que acontece em tempo de execução (por exemplo, uma conversão real de duplo para longo).C ++ tenta lidar com isso, pelo menos até certo ponto, adicionando um número de "novos" operadores de conversão, cada um dos quais é restrito a apenas um subconjunto dos recursos de uma conversão C. Isso torna mais difícil (por exemplo) fazer acidentalmente uma conversão que você realmente não pretendia - se você apenas pretende jogar fora a constância em um objeto, você pode usar
const_cast
e ter certeza de que a única coisa que pode afetar é se um objecto éconst
,volatile
ou não. Por outro lado, umstatic_cast
não pode afetar se um objeto éconst
ouvolatile
. Resumindo, você tem a maioria dos mesmos tipos de recursos, mas eles são categorizados de forma que um elenco geralmente pode fazer apenas um tipo de conversão, onde um único elenco de estilo C pode fazer duas ou três conversões em uma operação. A principal exceção é que você pode usar umdynamic_cast
no lugar destatic_cast
em pelo menos alguns casos e, apesar de ter sido escrito como umdynamic_cast
, na verdade terminará como umstatic_cast
. Por exemplo, você pode usardynamic_cast
para percorrer uma hierarquia de classes para cima ou para baixo - mas um elenco "para cima" na hierarquia é sempre seguro, portanto, pode ser feito estaticamente, enquanto um elenco "para baixo" na hierarquia não é necessariamente seguro, por isso é feito dinamicamente.Java e C # são muito mais semelhantes entre si. Em particular, com ambos a conversão é (virtualmente?) Sempre uma operação em tempo de execução. Em termos de operadores de cast C ++, geralmente é o mais próximo de a
dynamic_cast
em termos do que realmente é feito - ou seja, quando você tenta converter um objeto para algum tipo de destino, o compilador insere uma verificação em tempo de execução para ver se a conversão é permitida e lançar uma exceção se não for. Os detalhes exatos (por exemplo, o nome usado para a exceção de "elenco incorreto") variam, mas o princípio básico permanece basicamente semelhante (embora, se a memória não for suficiente, Java faz casts aplicados a alguns tipos de não-objeto, comoint
muito mais perto de C casts - mas esses tipos raramente são usados o suficiente para 1) Não me lembro com certeza e 2) mesmo que seja verdade, não importa muito de qualquer maneira).Olhando para as coisas de forma mais geral, a situação é bastante simples (pelo menos IMO): um elenco (obviamente o suficiente) significa que você está convertendo algo de um tipo para outro. Quando / se você fizer isso, surge a pergunta "Por quê?" Se você realmente quer que algo seja um tipo específico, por que não definiu como esse tipo para começar? Isso não quer dizer que nunca haja uma razão para fazer tal conversão, mas sempre que isso acontecer, deve perguntar se você poderia redesenhar o código para que o tipo correto fosse usado em todo o processo. Mesmo conversões aparentemente inócuas (por exemplo, entre número inteiro e ponto flutuante) devem ser examinadas muito mais de perto do que é comum. Apesar de sua aparênciasimilaridade, inteiros deveriam realmente ser usados para tipos de coisas "contados" e ponto flutuante para tipos de coisas "medidos". Ignorar a distinção é o que leva a algumas das afirmações malucas como "a família americana média tem 1,8 filhos". Embora todos possamos ver como isso acontece, o fato é que nenhuma família tem 1,8 filhos. Eles podem ter 1 ou 2 ou mais do que isso - mas nunca 1.8.
fonte
Muitas respostas boas aqui. Esta é a maneira como vejo isso (de uma perspectiva C #).
Casting geralmente significa uma de duas coisas:
Eu sei o tipo de tempo de execução desta expressão, mas o compilador não sabe disso. Compilador, estou lhe dizendo, em tempo de execução o objeto que corresponde a essa expressão será realmente desse tipo. A partir de agora, você sabe que esta expressão deve ser tratada como sendo desse tipo. Gere um código que presuma que o objeto será do tipo fornecido ou lance uma exceção se eu estiver errado.
Tanto o compilador quanto o desenvolvedor conhecem o tipo de tempo de execução da expressão. Existe outro valor de um tipo diferente associado ao valor que esta expressão terá no tempo de execução. Gere o código que produz o valor do tipo desejado a partir do valor do tipo fornecido; se você não puder fazer isso, lance uma exceção.
Observe que esses são opostos . Existem dois tipos de elencos! Existem casts onde você está dando uma dica para o compilador sobre a realidade - ei, essa coisa de tipo de objeto é na verdade do tipo Cliente - e existem casts onde você está dizendo ao compilador para executar um mapeamento de um tipo para outro - ei, Preciso do int que corresponde a esse duplo.
Ambos os tipos de elencos são bandeiras vermelhas. O primeiro tipo de elenco levanta a questão "por que exatamente o desenvolvedor sabe algo que o compilador não sabe?" Se você está nessa situação, então a melhor coisa a fazer é, geralmente, para alterar o programa para que o compilador não tem uma alça sobre a realidade. Então você não precisa do elenco; a análise é feita em tempo de compilação.
O segundo tipo de conversão levanta a questão "por que a operação não está sendo feita no tipo de dados de destino em primeiro lugar?" Se você precisa de um resultado em ints, por que está segurando um double em primeiro lugar? Você não deveria estar segurando um int?
Algumas idéias adicionais aqui:
http://blogs.msdn.com/b/ericlippert/archive/tags/cast+operator/
fonte
Foo
objeto que é herdado deBar
e o armazena em umList<Bar>
, então você vai precisar de casts se quiser isso deFoo
volta. Talvez indique um problema em um nível arquitetônico (por que estamos armazenandoBar
s em vez deFoo
s?), Mas não necessariamente. E, seFoo
também tiver um elenco válido paraint
, também lida com seu outro comentário: Você está armazenando umFoo
, não umint
, porque umint
nem sempre é apropriado.Foo
em aList<Bar>
, mas no ponto do elenco você está tentando fazer algoFoo
que não é apropriado para umBar
. Isso significa que o comportamento diferente para subtipos está sendo feito por meio de um mecanismo diferente do polimorfismo integrado fornecido por métodos virtuais. Talvez seja a solução certa, mas na maioria das vezes é uma bandeira vermelha.Tuple<X,Y,...>
tuplas é improvável que haja um uso generalizado em C #. Este é um lugar onde a linguagem poderia fazer um trabalho melhor de empurrar as pessoas para o "poço do sucesso".Erros de conversão são sempre relatados como erros de tempo de execução em java. O uso de genéricos ou modelos transforma esses erros em erros de tempo de compilação, tornando muito mais fácil detectar quando você cometeu um erro.
Como eu disse acima. Isso não quer dizer que todo elenco seja ruim. Mas se for possível evitá-lo, é melhor fazê-lo.
fonte
O elenco não é inerentemente ruim, apenas que muitas vezes é mal utilizado como um meio de alcançar algo que realmente não deveria ser feito ou feito de forma mais elegante.
Se fosse universalmente ruim, os idiomas não o suportariam. Como qualquer outro recurso de linguagem, ele tem seu lugar.
Meu conselho é que você se concentre em seu idioma principal e entenda todos os elencos e as melhores práticas associadas. Isso deve informar excursões em outras línguas.
Os documentos C # relevantes estão aqui .
Há um ótimo resumo sobre as opções de C ++ em uma questão anterior do SO aqui .
fonte
Estou falando principalmente de C ++ aqui, mas a maior parte disso provavelmente se aplica a Java e C # também:
C ++ é uma linguagem tipificada estaticamente . Existem algumas possibilidades que a linguagem permite (funções virtuais, conversões implícitas), mas basicamente o compilador conhece o tipo de cada objeto em tempo de compilação. A razão para usar tal linguagem é que os erros podem ser detectados em tempo de compilação . Se o compilador conhece os tipos de
a
eb
, então ele o pegará no tempo de compilação quando você fizera=b
ondea
é um número complexo eb
é uma string.Sempre que você faz uma conversão explícita, diz ao compilador para calar a boca, porque você acha que sabe melhor . Caso você esteja errado, normalmente só descobrirá em tempo de execução . E o problema de descobrir em tempo de execução é que pode ser na casa de um cliente.
fonte
Java, c # e c ++ são linguagens fortemente tipadas, embora as linguagens fortemente tipadas possam ser vistas como inflexíveis, elas têm a vantagem de fazer a verificação de tipo em tempo de compilação e protegem contra erros de tempo de execução causados por ter o tipo errado para certas operações.
Existem basicamente dois tipos de elenco: um elenco para um tipo mais geral ou um elenco para outros tipos (mais específico). A conversão para um tipo mais geral (conversão para um tipo pai) deixará as verificações de tempo de compilação intactas. Mas a conversão para outros tipos (tipos mais específicos) desabilitará a verificação de tipo em tempo de compilação e será substituída pelo compilador por uma verificação em tempo de execução. Isso significa que você tem menos certeza de que o código compilado será executado corretamente. Ele também tem um impacto insignificante no desempenho, devido à verificação de tipo de tempo de execução extra (a API Java está cheia de conversões!).
fonte
dynamic_cast
é geralmente mais lento do questatic_cast
, uma vez que geralmente tem que fazer a verificação de tipo em tempo de execução (com algumas ressalvas: converter para um tipo base é barato, etc).Alguns tipos de fundição são tão seguros e eficientes que muitas vezes nem mesmo são considerados fundidos.
Se você converter de um tipo derivado para um tipo base, isso geralmente é bem barato (frequentemente - dependendo da linguagem, implementação e outros fatores - tem custo zero) e é seguro.
Se você converter de um tipo simples, como um int para um tipo mais amplo, como um int longo, então, novamente, é muito barato (geralmente não é muito mais caro do que atribuir o mesmo tipo a esse cast) e novamente é seguro.
Outros tipos são mais complexos e / ou mais caros. Na maioria das linguagens, a conversão de um tipo base para um tipo derivado é barato, mas apresenta um alto risco de erro grave (em C ++, se você static_cast da base para o derivado, será barato, mas se o valor subjacente não for do tipo derivado, comportamento é indefinido e pode ser muito estranho) ou relativamente caro e com o risco de gerar uma exceção (dynamic_cast em C ++, conversão de base para derivada explícita em C # e assim por diante). Boxing em Java e C # é outro exemplo disso, e uma despesa ainda maior (considerando que eles estão mudando mais do que apenas como os valores subjacentes são tratados).
Outros tipos de conversão podem perder informações (um tipo inteiro longo para um tipo inteiro curto).
Esses casos de risco (seja de exceção ou de erro mais grave) e de despesa são razões para evitar o casting.
Uma razão mais conceitual, mas talvez mais importante, é que cada caso de elenco é um caso em que sua capacidade de raciocinar sobre a exatidão de seu código é bloqueada: cada caso é outro lugar onde algo pode dar errado e as maneiras pelas quais isso pode dar errado aumenta a complexidade de deduzir se o sistema como um todo vai dar errado. Mesmo que o elenco esteja comprovadamente seguro todas as vezes, provar isso é uma parte extra do raciocínio.
Finalmente, o uso intenso de moldes pode indicar uma falha em considerar bem o modelo de objeto ao criá-lo, usá-lo ou ambos: A conversão para frente e para trás entre os mesmos tipos frequentemente é quase sempre uma falha ao considerar as relações entre os tipos usava. Aqui não é tanto que os moldes sejam ruins, pois eles são um sinal de algo ruim.
fonte
Há uma tendência crescente para os programadores se apegarem a regras dogmáticas sobre o uso de recursos de linguagem ("nunca use XXX!", "XXX considerado prejudicial", etc), onde XXX varia de
goto
s a ponteiros paraprotected
membros de dados para singletons para objetos de passagem por valor.Seguir tais regras, em minha experiência, garante duas coisas: você não será um péssimo programador, nem será um grande programador.
Uma abordagem muito melhor é cavar e descobrir o núcleo de verdade por trás dessas proibições cobertor, e, em seguida, usar os recursos criteriosamente, com o entendimento de que não são muitas situações para as quais eles são a melhor ferramenta para o trabalho.
"Eu geralmente evito lançar tipos o máximo possível" é um bom exemplo de uma regra generalizada. Casts são essenciais em muitas situações comuns. Alguns exemplos:
typedef
s). (Exemplo:GLfloat
<-->double
<-->Real
.)std::size_type
->int
.)Certamente, existem muitas situações em que não é apropriado usar um gesso, e é importante aprendê-las também; Não vou entrar em muitos detalhes, pois as respostas acima indicaram bem alguns deles.
fonte
Para elaborar a resposta do KDeveloper , não é inerentemente seguro de tipo. Com a transmissão, não há garantia de que o que você está transmitindo e para o qual corresponderá e, se isso ocorrer, você receberá uma exceção de tempo de execução, o que é sempre ruim.
Especificamente em relação ao C #, porque inclui os operadores
is
eas
, você tem a oportunidade de (na maior parte) decidir se um elenco será bem-sucedido ou não. Por isso, você deve executar as etapas apropriadas para determinar se a operação será bem-sucedida ou não e prosseguir de forma adequada.fonte
No caso do C #, é preciso ter mais cuidado ao lançar, por causa dos overheads de boxing / unboxing envolvidos ao lidar com tipos de valor.
fonte
Não tenho certeza se alguém já mencionou isso, mas a conversão em C # pode ser usada de maneira bastante segura e geralmente é necessária. Suponha que você receba um objeto que pode ser de vários tipos. Usando a
is
palavra-chave, você pode primeiro confirmar se o objeto é realmente do tipo para o qual você está prestes a lançá-lo e, em seguida, lançar o objeto para esse tipo diretamente. (Não trabalhei muito com Java, mas tenho certeza de que existe uma maneira muito direta de fazer isso também).fonte
Você apenas lança um objeto para algum tipo, se 2 condições forem atendidas:
Isso significa que nem todas as informações que você possui estão bem representadas na estrutura de tipo que você usa. Isso é ruim, porque sua implementação deve incluir semanticamente seu modelo, o que claramente não acontece neste caso.
Agora, quando você faz um elenco, então isso pode ter dois motivos diferentes:
Na maioria dos idiomas, você se depara com a segunda situação muitas vezes. Genéricos como em Java ajudam um pouco, o sistema de templates C ++ ainda mais, mas é difícil de dominar e mesmo assim algumas coisas podem ser impossíveis ou simplesmente não valer o esforço.
Então, você poderia dizer, um elenco é um hack sujo para contornar seus problemas para expressar algum tipo de relacionamento específico em algum idioma específico. Hackers sujos devem ser evitados. Mas você nunca pode viver sem eles.
fonte
Geralmente, os modelos (ou genéricos) são mais seguros para tipos do que os cast. A esse respeito, eu diria que um problema com o elenco é a segurança de tipo. No entanto, há outra questão mais sutil associada especialmente ao downcasting: design. Do meu ponto de vista, pelo menos, o downcasting é um cheiro de código, uma indicação de que algo pode estar errado com meu projeto e que devo investigar mais. Por que é simples: se você "entender" as abstrações certas, simplesmente não precisa delas! A propósito, boa pergunta ...
Felicidades!
fonte
Para ser bem conciso, um bom motivo é a portabilidade. Arquitetura diferente que acomoda a mesma linguagem pode ter, digamos, ints de tamanhos diferentes. Portanto, se eu migrar do ArchA para o ArchB, que tem um int mais estreito, posso ver um comportamento estranho na melhor das hipóteses e falhas no seg na pior.
(Estou claramente ignorando bytecode independente de arquitetura e IL.)
fonte