Ponto V!

Home C/C++ Conceitos Básicos C++ Type Casting - 2ª Parte
Bruno Crivelari Sanches
C++ Type Casting - 2ª ParteImprimir
Escrito por Bruno Crivelari Sanches

No tutorial 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 tutorial 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ências

New Casts Revisited - Bjarne Stroustrup: a base de todo esse tutorial, contém vários exemplos de como utilizar e de como bagunçar tudo com os casts do C++.

Smart Pointers Type Casting - Operadores de casting que devem ser usados com as classes de smart pointers da boost.

Comentários (7)
  • Fernando  - Duvida
    avatar

    Olá Bruno

    Tenho acompanhado os seus tutoriais e tem me ajudado bastante mas estou fazendo o jogo masterMind em c++ e tenho tido alguns problemas com a verificação de pasição dos numeros. gostaria de saber se vc poderia me ajudar caso sim como faço para lhe enviar o codigo

  • Bruno Crivelari Sanches
    avatar

    Olá Fernando,

    é mais fácil e garantido usar um fórum, como o pdj ou unidev

    Participo de ambos e você não fica restrito a apenas minha ajuda.

    T+

  • Francisco  - dynamic_cast
    avatar

    Olá Bruno tenho acompanhado os seus tutoriais e tenho aprendido muito com eles. ao fazer um programa onde tento testar o tipo Base se uma class que suponho ser o que faz dynamic_cast deparo-me com setes dois tipos de erro:

    [cpp]
    int i = 10;
    string s = "dez";
    ClassTest* a = dynamic_cast (&i);
    ClassTest* b = dynamic_cast (&s);
    [/cpp]

    erro 1:cannot dynamic_cast '& i' (of type 'int*') to type 'class ClassTest*' (source is not a pointer to class)
    erro2:cannot dynamic_cast '& s' (of type 'struct std::string*') to type 'class ClassTest*' (source type is not polymorphic)

    no erro 1 pelo que percebi não posso usar tipos primitivos no dynamic_cast (mas como ultrapasso esse problema casa os queira usar?)
    o erro 2 nem percebi

    obrigado.

  • Bruno Crivelari Sanches
    avatar

    Oi Francisco!

    O dynamic_cast é apenas para conversão entre tipos de uma mesma hierárquia de classes. Não tem sentido para ele converter um tipo qualquer para uma classe.

    Se você realmente precisa fazer cast de um inteiro para um ponteiro de um outro tipo, a unica solução é usar o reinterpret_cast, mas o ponteiro só vai ter alguma utilidade se o valor do inteiro realmente for o endereço de memória de um objeto do tipo que você fez o cast.

    T+

  • Francisco
    avatar

    #ifndef DERIVADACLASSTEST_H_
    #define DERIVADACLASSTEST_H_
    #include "ClassTest.h"

    class DerivadaClassTest:public ClassTest{
    };

    #endif /* DERIVADACLASSTEST_H_ */


    #ifndef CLASSTEST_H_
    #define CLASSTEST_H_

    class ClassTest {
    public:
    template
    bool testeSuperClass(T &obj) {
    /*
    * Eu queria saber se obj é uma instância de ClassTest
    */
    ClassTest* ct = reinterpret_cast (&obj);
    if(ct)
    return true;
    return false;
    }
    };

    #endif /* CLASSTEST_H_ */

    #include
    #include "DerivadaClassTest.h"
    using namespace std;

    int main() {
    int i = 10;
    ClassTest ct;
    DerivadaClassTest dct;
    if (ct.testeSuperClass(i))
    cout

  • Bruno Crivelari Sanches
    avatar

    Agora entendi, você quer um método genérico para determinar se o tipo passado é de uma determinada classe.

    Até onde sei isso não vai ser possível, muto menos em tempo de execução.

    Para se checar um tipo você precisa usar o dynamic_cast, para usar o dynamic_cast você precisa de algo que possa ser aplicado o typeid, que só vai ser util em tipos que tenham o type_info, que são apenas classes com pelo menos um método virtual.

    Você vai ter que pelo menos adicionar especializações na template para driblar os tipos primitivos.

  • Anônimo
    avatar

    Ok. percebi, obrigado.

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter