quinta-feira, 16 de abril de 2015

Desvendando os "casts" (conversões de tipo) de C++

Este artigo é dedicado aos programadores iniciantes na linguagem C++, e trata sobre as diversas maneiras de converter objetos de um tipo para outro usando a sintaxe da linguagem.

Todo programador sabe que muitas vezes é preciso converter uma variável, estrutura ou objeto de um determinado tipo para outro, por qualquer motivo que seja. A antiga linguagem C, antepassada do C++, permite isso de uma maneira muito simples, bastando usar a seguinte sintaxe:

TipoA variavelDeTipoA = (TipoA) variavelDeTipoB

Até aí tudo bem, isso é um conceito básico da linguagem C. Acontece que em C++, sendo "herdeira" direta de C, herdou essa sintaxe de conversão de tipo. No entanto, C++ introduziu os seus próprios operadores de cast, com uma sintaxe específica que permite um melhor entendimento e uma maior visibilidade das conversões, além de torná-las mais seguras. Não estou me referindo à sintaxe alternativa dos casts de C, que podem ser escritos da seguinte forma (reescrevendo o exemplo anterior):

TipoA variavelDeTipoA = TipoA(variavelDeTipoB)

A confusão começa porque em C++ existem formas diferentes de cast, e cada uma tem um objetivo diferente. Se você não conhece e nunca os viu, saiba que a sintaxe é horrível, e supostamente isso é proposital. Confira abaixo as diferentes sintaxes de cast:

TipoA variavelDeTipoA = static_cast<TipoA>(variavelDeTipoB)
TipoA variavelDeTipoA = dynamic_cast<TipoA>(variavelDeTipoB)
TipoA variavelDeTipoA = const_cast<TipoA>(variavelDeTipoB)
TipoA variavelDeTipoA = reinterpret_cast<TipoA>(variavelDeTipoB)

Como você pode ver, os quatro operadores são static_cast, dynamic_cast, const_cast e reinterpret_cast. Essas palavras são acompanhadas do nome do tipo ao qual se deseja converter, entre os sinais de menor e maior, parecido com a sintaxe dos templates (pra quem já sabe o que são templates).

Cada um destes casts tem um significado diferente. Segue abaixo as situações em que você deve usar um ou outro:

static_cast

Se utiliza para conversões genéricas entre tipos, porém pode ser perigoso pois este operador não faz nenhuma verificação para garantir que a conversão seja segura, e é o programador que deve se assegurar disso. Por exemplo, com esse operador é possível converter ponteiros de classes diferentes numa mesma hierarquia, tanto de subclasse para superclasse (upcast) como de superclasse para subclasse (downcast), e esse segundo caso pode gerar bugs, já que depois de converter um ponteiro de superclasse para o tipo de uma subclasse você poderá estar acessando através dele um objeto incompleto (pois a superclasse normalmente tem menos membros do que a subclasse).

dynamic_cast

Se utiliza para conversões entre ponteiros de objetos de uma mesma hierarquia de classes. Esse operador é mais seguro do que o static_cast, já que com ele o ponteiro só será convertido se essa conversão for segura. Em caso da conversão falhar, ele retorna NULL. É possível fazer tanto upcasts quanto downcasts, porém diferente de static_cast, você só consegue fazer downcast se o objeto a ser convertido for um objeto válido e completo do tipo ao qual se estiver convertendo.

const_cast

Esse é um operador de conversão um tanto curioso. Ele serve para "tirar o const" dos objetos. Se você tem um objeto que foi declarado como const, ou seja, cujo valor não pode ser modificado, você pode usar const_cast para fazer com que ele deixe de ser const, ou seja, para que você possa modificá-lo. É praticamente uma forma de burlar o sistema: o objeto foi declarado de uma forma que não possa ser modificado, mas mesmo assim eu quero modificá-lo, então eu pego ele, dou um const_cast e pronto.

reinterpret_cast

Se você achou const_cast estranho, você vai se indignar com este. Reinterpret_cast é um operador de cast onipotente, com ele você pode fazer tudo. Você pode converter um unsigned double em um char, um ponteiro da classe Galinha para um ponteiro da classe Reptil, ou seja, não interessam os tipos dos objetos sendo convertidos. Esse operador converte qualquer coisa em qualquer outra coisa. Resumindo, reinterpret_cast é na maior parte dos casos, inútil. Nunca na vida precisei usar. Já ouvi falar que ele é útil em casos onde o que interessa é a representação binária dos objetos, algo extremamente baixo-nível.

E esses foram os casts específicos do C++. Resumindo: em geral você vai se contentar com static_cast para conversões com tipos primitivos, ponteiros para void, e outras conversões genéricas que não envolvam classes polimórficas, e dynamic_cast para converter entre ponteiros de classes polimórficas. O const_cast pode ser uma raridade na vida, e o reinterpret_cast você pode basicamente esquecer que ele existe (mas se alguém te perguntar, você lembra).

Espero que tenham gostado!