Menu

Conversão de tipos em C++: static_cast, conversões implícitas e casts

Como funciona a conversão de tipos em C++: conversões implícitas, a armadilha da divisão inteira e os quatro casts nomeados (static_cast, const_cast, reinterpret_cast, dynamic_cast), com os detalhes que causam perda silenciosa de dados.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O que é conversão de tipos

Converter tipos significa transformar um valor de um tipo em outro: transformar um double em um int, um char em seu código numérico, ou um ponteiro para a classe base em um para a classe derivada. O C++ faz algumas dessas conversões por você automaticamente, mas as que ele faz em silêncio são exatamente onde os bugs se escondem.

Existem dois sabores: conversões implícitas que acontecem sozinhas e casts explícitos que você mesmo escreve. Você já viu um sintoma disso em operadores: a divisão inteira. O cast é como você assume o controle disso.

Conversões implícitas

Quando você mistura tipos numéricos em uma expressão, o C++ promove o tipo "menor" para o "maior" para que os dois lados combinem. Geralmente isso faz o que você quer.

O problema começa quando a conversão vai no sentido oposto: de um tipo mais largo para um mais estreito. Isso é uma conversão de estreitamento, e ela pode perder dados em silêncio.

De float para int, trunca em direção a zero: não arredonda, então 3.99 vira 3. E enfiar 300 em um char causa estouro. Muitos compiladores avisam aqui; alguns não. Quando você realmente quiser estreitar, diga isso explicitamente com um cast para que o próximo leitor saiba que foi de propósito.

Corrigindo a armadilha da divisão inteira

O motivo mais comum para fazer um cast é a divisão. Quando os dois operandos são inteiros, / faz divisão inteira e descarta o resto.

A correção é static_cast<double> em um operando antes da divisão. Um erro frequente é static_cast<double>(got / total): é tarde demais, porque got / total já vale 0 quando o cast roda, então você obtém 0.0. Faça o cast de um operando, não do resultado.

static_cast: seu cast padrão

O C++ oferece quatro casts nomeados. O que você vai usar em 95% das vezes é static_cast<T>(value), que realiza conversões bem definidas entre tipos relacionados: conversões numéricas, de enum para int, de void* de volta para um ponteiro tipado, e subir ou descer em uma hierarquia de classes quando você já conhece o tipo.

Prefira static_cast ao antigo cast no estilo C (int)balance. Um cast no estilo C vai tentar qualquer conversão para fazer o código compilar, incluindo as perigosas abaixo, então ele pode remover o const em silêncio ou reinterpretar bytes brutos. O static_cast só permite conversões que o compilador consegue de fato justificar, e o verboso static_cast<...> é trivial de localizar em uma revisão de código.

// Evite - cast no estilo C, sem rede de segurança:
int dollars = (int) balance;

// Prefira - explícito, verificado, fácil de localizar:
int dollars = static_cast<int>(balance);

Os outros três casts (use com parcimônia)

Os casts restantes existem para tarefas específicas e bem estreitas. Recorra a eles apenas quando o static_cast realmente não der conta.

const_cast remove (ou adiciona) const. Seu único uso legítimo é chamar uma API no estilo C que esqueceu de marcar um parâmetro como const. Modificar, através de um const_cast, um objeto que foi originalmente declarado como const é comportamento indefinido.

void legacyApi(char* msg);   // API antiga, não aceita const

const char* text = "hello";
legacyApi(const_cast<char*>(text));   // só é seguro se legacyApi não escrever nele

reinterpret_cast reinterpreta o padrão de bits bruto, por exemplo um ponteiro como um endereço inteiro. Não realiza nenhuma conversão e é extremamente inseguro; quase sempre é um sinal de que você deveria repensar o design.

dynamic_cast converte com segurança um ponteiro ou referência da classe base para um tipo derivado em tempo de execução, usando o tipo real do objeto. Requer uma base polimórfica (uma classe com pelo menos uma função virtual) e retorna nullptr se o cast não se aplicar.

Se a tivesse apontado para outro Animal, dynamic_cast<Dog*> retornaria nullptr e o ramo else rodaria, que é exatamente por que ele é mais seguro do que usar static_cast às cegas para descer em uma hierarquia.

Erros comuns a evitar

  • Fazer o cast do resultado em vez de um operando. static_cast<double>(a / b) descarta sua fração primeiro. Faça o cast de a ou b.
  • Supor que de float para int arredonda. Ele trunca: static_cast<int>(2.99) é 2. Para arredondar, use std::round, std::lround, etc.
  • Recorrer a um cast no estilo C. Ele esconde qual conversão acontece. Use static_cast e você receberá um erro de compilação quando a conversão for insegura, em vez de uma surpresa silenciosa.
  • Estreitar para um tipo pequeno demais. Fazer o cast de 300 para char ou de um long enorme para int dá a volta ou estoura. Escolha um tipo de destino largo o bastante para o intervalo.

Próximo: If-Else

Agora que você consegue converter e comparar valores com clareza, o próximo passo é tomar decisões com eles. A instrução if-else executa código diferente dependendo de uma condição ser true: a base de todo programa com ramificações.

Perguntas frequentes

Qual é a diferença entre static_cast e um cast no estilo C em C++?

Um cast no estilo C como (int)x tenta cada conversão por vez: pode se tornar silenciosamente um perigoso reinterpret_cast ou remover o const. static_cast<int>(x) só realiza conversões relacionadas que o compilador consegue verificar, então o compilador rejeita absurdos. Em C++ moderno, prefira sempre static_cast aos casts no estilo C; é mais seguro e muito mais fácil de localizar com grep.

Como faço o cast de um int para um double em C++?

Use static_cast<double>(x). Isso importa principalmente na divisão: 5 / 2 é divisão inteira e dá 2, mas static_cast<double>(5) / 22.5. Faça o cast de um dos operandos antes de a divisão acontecer: fazer o cast do resultado, static_cast<double>(5 / 2), é tarde demais e ainda dá 2.0.

Por que fazer o cast de um valor grande para um tipo menor dá um número errado em C++?

Converter para um tipo que não consegue armazenar o valor é uma conversão de estreitamento. De float para int, a parte fracionária é truncada (static_cast<int>(3.99) é 3), e um inteiro fora do intervalo ou dá a volta (sem sinal) ou fica definido pela implementação (com sinal). O compilador geralmente não te impede, então faça o cast de forma deliberada e garanta que o tipo de destino seja largo o bastante.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR