C++ Type Casting – 2ª Parte

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

Uma resposta para “C++ Type Casting – 2ª Parte”

  1. Caloni.com.br » Blog Archive » Últimas pesquisas na blogosfera nacional Disse:

    [...] diversos tipos de [...]

Deixe uma resposta