No post anterior vimos como funciona o casting que o C++ herdou da linguagem C, agora vamos ver como funcionam os novos operadores de casting do C++
static_cast
O static_cast é o mais simples de todos, ele faz em partes o trabalho do cast do C, mas com algumas restrições que veremos na sequência. Baseando-se no exemplo do post anterior onde tínhamos as classes Objeto, Monstro e Tiro pode-se construir:
void CriaExplosao(int potencia);
void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = static_cast<Tiro *>(obj);
CriaExplosao(tiro->Potencia());
}
Por alguma obra do destino que não vem ao caso (ou por falta de criatividade do autor com exemplos) a função ExplodeTiro recebe como parâmetro uma referência para Objeto, e não tiro. Por se tratar da função ExplodeTiro sabemos e confiamos friamente que os programadores sempre vão usar ela passando como parâmetro uma classe Tiro.
O static_cast não faz verificação nenhuma para checar se o Objeto passado como parâmetro é da classe Tiro ou não, apenas ajusta algum endereço se necessário, nada mais. Agora vamos imaginar que um novo programador começou a trabalhar no projeto e sem saber das consequências escreveu:
void func()
{
Monstro m;
ExplodeTiro(&m);
}
O que vai acontecer? Se da ultima vez o cachorro da vizinha latiu, dessa vez é provável que ele exploda, quem sabe o cãozinho que faleceu segundo relataram alguns leitores não ressuscite com essa técnica? Piadas a parte, o comportamento é totalmente imprevisível nesse caso.
A vantagem de se usar o static_cast é que o compilador faz algumas verificações antes de usá-lo:
Tiro tiro; Monstro *m = static_cast<Monstro *>(&tiro);
O código acima vai dar erros, porque Tiro e Monstro são classes que não podem ser convertidas entre si. Se fosse utilizado um cast do C o compilador aceitaria tudo sem problemas.
Outra vantagem do static_cast é que ele somente permite seu uso com tipos definidos:
class X;
class Y;
void proc(X *x)
{
Y *p = static_cast<Y *>(x);
}
Esse é o mesmo exemplo do artigo do Bjarne listado nas referências abaixo, o interessante desse código é que ele não compila, pois o compilador ainda não conhece a estrutura das classes X e Y, evitando assim a geração de código que pode fazer alguem perder a noite com um bug misterioso.
O static_cast também evita erros como remoção de const por acidente, acesso a membros privados, etc. Dessa forma ele impõe uma série de restrições ao código, minimizando erros que poderiam passar despercebidos.
dynamic_cast
Este cast é utilizado quando é preciso fazer um cast de uma classe base para uma classe derivada. No caso da função ExplodeTiro acima, podemos melhora-la trocando o static_cast pelo dynamic_cast:
void ExplodeTiro(Objeto &obj)
{
Tiro *tiro = dynamic_cast<Tiro *>(&obj);
if(tiro == NULL)
return;
CriaExplosao(tiro->Potencia());
}
Note que caso a conversão não seja possível, o dynamic_cast retorna NULL, dando oportunidade ao programador de verificar se o tipo é valido ou não. No caso da função acima, decidimos simplesmente parar a execução da função quando não é possível fazer a conversão.
O dynamic_cast é o cast mais complexo em termos computacionais do C++, pois ele precisa realizar algumas buscas pela hierarquia de objetos e isso algumas vezes pode levar um tempo precioso, sendo assim, deve-se usá-lo com moderação.
Agora no dia a dia, se o programador tiver certeza absoluta de que o tipo a fazer cast é da classe derivada, não existe problema algum em usar o static_cast ao invés do dynamic_cast (a não ser o risco de fazer coisa errada). Existem códigos onde ao invés dos casts, existe uma template onde na versão debug do código é usado um dynamic_cast com assert, na versão release é usado apenas um static_cast.
Outro detalhe do dynamic_cast é que para ele funcionar é necessário ligar a geração de RTTI (Run Time Type Information – Informação de Tipos em Tempo de Execução), que é opcional em alguns compiladores (Visual Studio por exemplo). Pode ser que algum compilador esse item não seja opcional, mas para o dynamic_cast funcionar ele tem que existir. No caso do visual, se alguem tenta usar o dynamic_cast sem RTTI ele vai gera um erro em tempo de compilação.
reinterpret_cast
Este cast é utilizado quando queremos converter um tipo para um outro tipo não relacionado, como por exemplo de char* para int*. Ele é apenas uma indicação ao compilador de que você sabe o que esta fazendo. Note que ele também pode ser usado para tipos que ainda não foram definidos, mas atenção, o reinterpret_cast diferentemente do dynamic_cast não navega pela hierarquia de classes. Exemplos:
void ExplodeTiro(Objeto *obj)
{
Tiro *tiro = reinterpret_cast<Tiro *>(obj);
CriaExplosao(tiro->Potencia());
}
No exemplo acima o código compila, mas a não ser que a intenção seja realmente essa, esse código somente vai dar problemas. No primeiro cast da classe Objeto para Tiro quando o reinterpret_cast é usado os ponteiros não são ajustados. O ajuste de ponteiros é feito quando trocamos ponteiros de uma classe filha para uma classe pai ou vice versa, isso depende da implementação, mas baseado no exemplo, o ponteiro para Tiro pode não possuir exatamente o mesmo endereço do ponteiro para Objeto (mesmo se tratando do mesmo objeto). Geralmente em hierarquias simples (sem herança múltipla ou herança virtual) o endereço é sempre o mesmo.
O reinterpret_cast é recomendado apenas para operações onde se deseja converter um tipo básico para ponteiro e vice-versa:
void func()
{
int i = 5;
char *p = reinterpret_cast<char *>(i);
++p;
i = reinterpret_cast<int>(p);
}
Outro detalhe é que o reinterpret_cast também preserva constness de tipos assim como o static_cast.
const_cast
O ultimo tipo e o mais simples de todos é o const_cast, este cast simplesmente tira o const de um tipo:
void ExplodeTiro(const Objeto *obj)
{
Tiro *tiro = const_cast<Tiro *>(static_cast<const Tiro *>(obj));
CriaExplosao(tiro->Potencia());
//um cast tipico apenas para remover o cont
Objeto *o = const_cast<Objeto *>(obj);
}
Note que foi usado um static_cast que converte para “const Tiro *”, pois se fosse usado dentro do static_cast apenas “Tiro *” o compilador iria reclamar, então com o resultado dele é aplicado o const_cast, que remove o const do tipo. As pessoas reclamam que é muito código para pouca coisa, mas em troca disso temos um código mais seguro, além de ficar bem explicito quais são as intenções do programador e para terminar, essas operações não implicam custos extras em relação a um cast tradicional do C, então o uníco motivo para não usar é preguiça na hora de digitar.
Sobre o exemplo acima, o ideal é mudar o método potência e transforma-lo em um método const, mas vamos supor que isso não fosse possível por alguma outra obra do acaso
.
Conclusões
Os casts do C++ trazem mais segurança ao programador e ajudam a evitar erros de conversões invalidas. Apesar de eles serem muito mais longos que um cast normal (em termos de tamanho de código a se digitar) é vantajoso utiliza-los em vista das vantagens que eles trazem.
Caso esteja em duvida sobre qual cast utilizar, utilize o static_cast, se o compilador reclamar:
- Se for um const sendo removido, estude o caso e veja se é realmente necessário, caso sim, utilize o const_cast.
- Se for algum tipo incompleto, adicione o include apropriado para definir o tipo ou veja se é necessário trocar por um reinterpret_cast.
Referência
New Casts Revisited – Bjarne Stroustrup: a base de todo esse post, contém vários exemplos de como utilizar e de como bagunçar tudo com os casts do C++.
Maio 8, 2009 às 7:51 |
[...] diversos tipos de [...]