02.02
2021
Casting é a conversão entre tipos de valores. Em C++ a conversão de valores é um pouco mais complicada que em C porque existe a questão da herança de classes, por conta disso temos outros quatro tipo de cast em C++: dynamic_cast, static_cast, const_cast e reinterpret_cast.
dynamic_cast
Este cast verifica a validade da conversão, falhando quando for inválido. O principal uso é efetuar a conversão de tipos polimórficos. Imagine duas classes, Inseto, e sua classe derivada Formiga. Um dynamic_cast sempre pode converter um ponteiro de Formiga para um ponteiro de Insto, afinal essa é uma das principais funcionalidades do polimorfismo.
Mas um ponteiro do tipo Inseto somente vai poder ser convertido em um ponteiro do tipo Formiga quando o objeto apontado por Inseto for efetivamente do tipo Formiga. Vamos tentar ilustrar melhor com dois exemplos:
#include <iostream>
#include <string>
class Mammal {
public:
virtual std::string getName() { return "Mammal"; }
};
class Cat : public Mammal {
public:
virtual std::string getName() { return "Cat"; }
virtual std::string meow() { return "Meow"; }
};
class Dog : public Mammal {
public:
virtual std::string getName() { return "Dog"; }
virtual std::string wag() { return "Tail"; }
virtual std::string bark() { return "Wouf"; }
};
int main() {
Cat cat;
Cat *pcat = NULL;
Mammal *pmammal = NULL;
Dog *pdog = NULL;
std::cout << "*** COMMON CAST ***" << std::endl;
pmammal = (Mammal*)&cat;
std::cout << "pmammal->getName(): " << pmammal->getName() << std::endl;
pcat = (Cat*)pmammal;
std::cout << "pcat->getName(): " << pcat->getName() << std::endl;
std::cout << "pcat->meow(): " << pcat->meow() << std::endl;
pdog = (Dog*)&cat;
if (pdog) {
std::cout << "pdog->getName(): " << pdog->getName() << std::endl;
std::cout << "pdog->bark(): " << pdog->bark() << std::endl;
}
else {
std::cout << "Cannot convert a cat into a dog" << std::endl;
}
}
No exemplo acima criamos três classes, Mammal (mamífero), Cat (gato) e Dog (cachorro). Ao tentar utilizar o polimorfismo atribuindo um objeto Cat a um ponteiro Mammal, tudo funciona direitinho nos dois sentidos, inclusive. Porém ao atribuir um objeto Cat a um ponteiro Dog não há nenhum tipo de warning ou de erro, e quando tentamos acessar um método que não existe na classe Cat o resultado é imprevisível. Veja a saída:
*** COMMON CAST ***
pmammal->getName(): Cat
pcat->getName(): Cat
pcat->meow(): Meow
pdog->getName(): Cat
pdog->bark():
O programa crashou exatamente durante a execução de Dog::bark(). Poderia até funcionar, mas o resultado é imprevisível.
Utilizando dynamic_cast o código ficaria assim:
int main() {
Cat cat;
Cat *pcat = NULL;
Mammal *pmammal = NULL;
Dog *pdog = NULL;
std::cout << "*** DYNAMIC CAST ***" << std::endl;
pmammal = dynamic_cast<Mammal*>(&cat);
std::cout << "pmammal->getName(): " << pmammal->getName() << std::endl;
pcat = dynamic_cast<Cat*>(pmammal);
std::cout << "pcat->getName(): " << pcat->getName() << std::endl;
std::cout << "pact->meow(): " << pcat->getName() << std::endl;
pdog = dynamic_cast<Dog*>(pmammal);
if (pdog) {
std::cout << "pdog->getName(): " << pdog->getName() << std::endl;
std::cout << "pdog->bark(): " << pdog->bark() << std::endl;
}
else {
std::cout << "cannot convert a cat into a dog" << std::endl;
}
}
Note que no código acima não temos a declaração de Mammal, Cat nem Dog.
Neste caso a saída vai ficar assim:
*** DYNAMIC CAST ***
pmammal->getName(): Cat
pcat->getName(): Cat
pact->meow(): Cat
cannot convert a cat into a dog
Note que uma conversão inválida foi “notificada” através de um ponteiro nulo. Ou seja, basta testar o resultado do dynamic_cast para verificar se a conversão é válida.
A utilização de dynamic_cast é recomendada sempre que as funcionalidades de polimorfismo é utilizada, principalmente para evitar atribuições inválidas, evitando, consequentemente, comportamentos erráticos. É uma prática muito bem aceita na maior parte dos times.
static_cast
Este é o mais simples dos casts, também equivalente ao cast tradicional do C ANSI. A grande diferença é segurança de tipo, com a devida execução de construtores e sobracargas aplicáveis.
Quando a conversão é feita entre tipos básicos, ou ponteiros de tipos básicos, não há diferença.
A maioria das equipes prefere static_cast ao cast tradicional, por uma questão de segurança.
const_cast
Muitos desenvolvedores consideram o const_cast uma trapaça. Ele pode ser utilizado para burlar variáveis const, removendo o “atributo” da variável. Vamos dar dois exemplos pra ilustrar melhor:
int sqr(const int &x) {
x = x * x;
return x;
}
O código acima não compila, porque o argumento const int &x não aceita atribuição. Para “corrigir” o problema basta fazer uso do const_cast:
#include <iostream>
int sqr(const int &x) {
return const_cast<int &>(x) = x * x;
}
int main() {
int n = 10;
std::cout << "sqr(): " << sqr(n) << std::endl;
std::cout << "n: " << n << std::endl;
}
Esse exemplo acima compila sem nem gerar warnings. E a saída:
sqr(): 100
n: 100
Note que a variável n, apesar de ter sido utilizada como argumento const, foi alterado.
Este cast é bem controverso, porque apesar de resolver o “problema” de uma variável const ele pode incorrer em uma falha de semântica. Se um argumento é const, é esperado que seu conteúdo não seja alterado. Por conta disso muitas equipes não aceitam sua utilização.
reinterpret_cast
Este é o cast mais perigoso, ele faz conversões sem considerar quais as consequências nem compatibilidades. As únicas restrições são as compatibilidades de tipos básicos, afora isso quaisquer conversões são executadas.
#include <iostream>
int main() {
char p[] = "Hello World!";
long long i = reinterpret_cast<long long>(p);
std::cout << "i: " << i << std::endl;
}
No caso acima a saída será o endereço numérico do primeiro caractere da string Hello World!. A conversão será feita independente deste número ter sentido ou não dentro do escopo do programa.
O reinterpret_cast não é muito bem aceito em muitas equipes de desenvolvimento, pois permite que ações muito arriscadas possam ser executadas sem nenhum tipo de warning ou erro. Por conta disso mesmo deve ser utilizado com muito cuidado.