Por que o casting deve ser evitado? [fechadas]


97

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 ++?


3
De onde você tirou essa impressão?
Oded em

8
Tive essa impressão porque nunca li um livro ou conheci um programador que dissesse "CASTING SOOOO GOOOOD !!!"
LoudNPossiblyWrong

15
A resposta para C ++ é inevitavelmente diferente da resposta para C #. Isso é muito específico do idioma. As respostas até agora respondem a esta pergunta para idiomas específicos e, em alguns casos, não indicam de que idioma estão falando.
James McNellis

2
Ruim é um termo relativo. Evitar o casting é uma prática recomendada, mas às vezes um programador tem que fazer o que um programador tem que fazer. (especialmente se você está escrevendo um programa java 1.5+ que usa uma biblioteca escrita para 1.4) Talvez renomeie a pergunta, "Por que o casting deve ser evitado?"
Mike Miller

2
Esta é uma ótima pergunta .... Ela foi fechada por 5 usuários, cada um com menos de 10k de reputação !! Um uso exagerado de novos poderes, claramente. Votou para reabrir.
reach4thelasers de

Respostas:


141

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_caste ter certeza de que a única coisa que pode afetar é se um objecto é const, volatileou não. Por outro lado, um static_castnão pode afetar se um objeto é constouvolatile. 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 um dynamic_castno lugar de static_castem pelo menos alguns casos e, apesar de ter sido escrito como um dynamic_cast, na verdade terminará como um static_cast. Por exemplo, você pode usar dynamic_castpara 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_castem 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, como intmuito 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.


10
Parece que você está sofrendo 'morte por atenção' aqui, esta é uma boa resposta.
Steve Townsend

2
Uma resposta forte, mas algumas das partes "Não sei" podem ser ajustadas para torná-la abrangente. @ Dragontamer5788 é uma boa resposta, mas não abrangente.
M2tM

5
Uma criança inteira e uma criança com uma perna faltando seriam 1,8 crianças. Mas, apesar disso, uma resposta muito boa. :)
Oz.

1
Uma resposta muito simpática que não foi minada pela exclusão de casais sem filhos.

@Roger: não excluindo, apenas ignorando.
Jerry Coffin

48

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/


2
Eu não acho que essas sejam bandeiras vermelhas inerentemente. Se você tem um Fooobjeto que é herdado de Bare o armazena em um List<Bar>, então você vai precisar de casts se quiser isso de Foovolta. Talvez indique um problema em um nível arquitetônico (por que estamos armazenando Bars em vez de Foos?), Mas não necessariamente. E, se Footambém tiver um elenco válido para int, também lida com seu outro comentário: Você está armazenando um Foo, não um int, porque um intnem sempre é apropriado.
Mike Caron

6
@Mike Caron - Não posso responder pelo Eric, obviamente, mas para mim uma bandeira vermelha significa "isso é algo para se pensar", não "isso é algo errado". E não há problema em armazenar um Fooem a List<Bar>, mas no ponto do elenco você está tentando fazer algo Fooque não é apropriado para um Bar. 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.
Jeffrey L Whitledge

14
De fato. Se você está retirando coisas de uma lista de animais e depois precisa dizer ao compilador, ah, a propósito, eu sei que o primeiro é um tigre, o segundo é um leão e o terceiro é um urso, então você deveria estar usando uma Tupla <Leão, Tigre, Urso>, não uma Lista <Animal>.
Eric Lippert

5
Eu concordo com você, mas acho que sem um melhor suporte sintático para 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".
kvb

4
@kvb: Eu concordo. Pensamos em adotar uma sintaxe de tupla para C # 4, mas ela não se encaixou no orçamento. Talvez em C # 5; ainda não elaboramos o conjunto completo de recursos. Muito ocupado reunindo o CTP para assíncrono. Ou talvez em uma versão futura hipotética.
Eric Lippert de

36

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.


4
Isso pressupõe que todo o elenco é "ruim"; no entanto, isso não é totalmente verdade. Tome C #, por exemplo, com suporte a cast implícito e explícito. O problema surge quando é executado um elenco que (acidentalmente) remove informações ou segurança de tipo (isso difere por idioma).

5
Na verdade, isso é totalmente errado em C ++. Talvez seja necessária uma edição para incluir os idiomas direcionados a essas informações.
Steve Townsend

@Erick - Sim, mas não sei nada sobre Java, nem detalhes C # suficientes para ter certeza de que as informações estão corretas.
Steve Townsend

Desculpe, sou um cara de Java, então meu conhecimento de c ++ é limitado o suficiente. Eu poderia editar a palavra "modelagem" a partir dele, pois pretendia ser vago.
Mike Miller

Não tão. Alguns erros de conversão são detectados pelo compilador Java, e não apenas pelos genéricos. Considere (String) 0;
Marquês de Lorne

18

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 .


9

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 ae b, então ele o pegará no tempo de compilação quando você fizer a=bonde aé um número complexo e bé 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.


5

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!).


5
Nem todos os casts "ignorar" tipo de segurança em C ++.
James McNellis

4
Nem todos os casts "ignorar" tipo de segurança em C #.

5
Nem todos os casts "ignoram" o tipo de segurança em Java.
Erick Robertson

11
Nem todos os moldes de segurança do tipo "bypass" em Casting. Oh espere, essa tag não se refere a um idioma ...
sbi

2
Em quase todos os casos em C # e Java, a conversão causará uma degradação do desempenho, pois o sistema executará uma verificação de tipo em tempo de execução (que não é gratuita). Em C ++, dynamic_casté geralmente mais lento do que static_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).
Travis Gockel

4

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.


2

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 gotos a ponteiros para protectedmembros 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:

  1. Ao interoperar com código de terceiros (especialmente quando esse código está repleto de typedefs). (Exemplo: GLfloat<--> double<--> Real.)
  2. Casting de um derivado para ponteiro / referência de classe base: Isso é tão comum e natural que o compilador fará isso implicitamente. Se torná-lo explícito aumenta a legibilidade, o elenco é um passo à frente, não para trás!
  3. Casting de um ponteiro / referência de classe base para classe derivada: Também comum, mesmo em código bem projetado. (Exemplo: recipientes heterogêneos.)
  4. Serialização / desserialização binária interna ou outro código de baixo nível que precisa de acesso aos bytes brutos de tipos integrados.
  5. A qualquer momento quando for simplesmente mais natural, conveniente e legível usar um tipo diferente. (Exemplo: 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.


1

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 ise as, 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.


1

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.


1

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 ispalavra-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).


1

Você apenas lança um objeto para algum tipo, se 2 condições forem atendidas:

  1. você sabe que é desse tipo
  2. o compilador não

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:

  1. Você fez um péssimo trabalho ao expressar os relacionamentos de tipo.
  2. o sistema de tipos de linguagens simplesmente não é expressivo o suficiente para formulá-las.

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.


0

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!


0

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.)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.