C++ cast
Table of contents
Conversão de tipos
O C++ é uma linguagem dita strictly typed, termo geralmente traduzido como fortemente tipada, isto significa que o tipo das variáveis é sempre certo e as operações devem ser definidas para tipos específicos. Gerar um objeto de um tipo através de uma expressão de outro tipo envolve uma conversão, ou casting.
Conversão estática
static_cast<novo_tipo>( expressão );
Esta é a conversão mais comum. É dita estática pois sua validade é analisada durante a compilação, as principais possibilidades são:
- Conversão implícita entre os tipos (como float pra int).
- Chamar um construtor do novo_tipo através do resultado da expressão.
- Usar operador de conversão definido pelo usuário do resultado da expressão para o novo_tipo.
- Converter ponteiros entre hierarquia de classes, desde que as classes
não sejam virtuais. (
static_cast
não verifica a validade da conversão durante a execução.) - Converter ponteiros void* para qualquer outro tipo de ponteiro. (resultado indefinido se o alinhamento do ponteiro não for correto.)
Conversão dinâmica
dynamic_cast<novo_tipo>( expressão );
Esta conversão é especial para referências ou ponteiros de objetos polimórficos (classes contendo funções virtuais). É dita dinâmica pois verifica durante a execução do programa se a conversão é válida quando descendo na hierarquia das classes. Em principal:
- Ao converter ponteiros pra cima na hierarquia (
expressão
é derivada denovo_tipo
), comporta-se como uma conversão implícita. - Ao converter ponteiros pra baixo na hierarquia, (
expressão
é base denovo_tipo
), verifica seexpressão
originalmente referia-se a um ponteiro paranovo_tipo
e, se sim, retorna o ponteiro ajustado. Caso a verificação falhe, retornanullptr
. - Conversão entre referências é semelhante, mas gera exceção
std::bad_cast
em caso de falha.
Conversão de constância
const_cast<novo_tipo>(expressão);
- Esta conversão tem a única função de adicionar ou remover a propriedade
const
. - Qualquer conversão pode gerar uma referência ou ponteiro para um
objeto para que este seja tratado como constante, mas apenas
const_cast
pode gerar uma nova referência ou ponteiro para um objeto constante para que este seja tratado como modificável (não-constante). - Esta conversão não gera nenhuma instrução, é apenas uma diretiva para o compilador.
conversão por reinterpretação
reinterpret_cast<novo_tipo>(expressão);
Esta conversão é que mais se distancia da característica fortemente
tipada da linguagem C++, pois comanda o compilador a reinterpretar
o resultado da expressão
como se fosse do novo_tipo
, em geral sem
realizar nenhuma operação nem verificação sobre os valores sendo
convertidos. A grosso modo, reinterpret_cast
é forma de dizer ao
compilador: “Confie em mim, esses números que estou lhe passando são o que
digo serem” . Seus principais usos são:
- Converter ponteiro ou referência para qualquer tipo de objeto para ponteiro ou referência para qualquer outro tipo de objeto.
- Converter um ponteiro para um número inteiro.
- Converter um número inteiro para ponteiro.
Quando usar static_cast
?
Procure usar a conversão que melhor expressa suas intenções. Como apenas a definição de cada tipo de conversão pode não ser muito elucidativa, seguem exemplos de cada uma:
int i = 5;
float x = i; //conversão implícita
float y = static_cast<float>(i); //conversão explícita
As conversões implícitas facilitam lidar com variáveis numéricas
e locais, mas é recomendado sempre explicitar a conversão quando exportando
a variável para alguma função. No exemplo abaixo, o resultado com e sem
o cast
é o mesmo (logo depois apontarei um possível problema):
int f(int x) { return x*2; }
float x = 1.f;
std::cout << "Sem cast : " << f(x) << std::endl; // Sem cast : 2
std::cout << "Com cast : " << f(static_cast<int>(x)) << std::endl; // Com cast : 2
Agora, digamos que seja introduzida uma nova função, sem alterar as partes existentes do código anterior:
int f(double x) { return x*5; }
float x = 1.f;
std::cout << "Sem cast : " << f(x) << std::endl; // Sem cast : 5
std::cout << "Com cast : " << f(static_cast<int>(x)) << std::endl; // Com cast : 2
O motivo disso é que a conversão implícita de float
para double
é preferida ao invés da conversão implícita de float
para int
.
Embora requerida em alguns casos (como convertendo ponteiros void*
para outros tipos) static_cast
tem uma função que pende mais para boa
organização e manutenção do código.
Quando usar dynamic_cast
?
dynamic_cast
é a forma de verificar em tempo de execução se o tipo de
objeto polimórfico passado é de um determinado tipo (para isso o programa faz
uso de RTTI, omitirei detalhes). Em um exemplo simplificado, imagine uma
classe base com dois tipos de derivadas:
struct Base {
virtual ~Base(){};
};
struct DerivadaA : public Base {
};
struct DerivadaB : public Base {
};
Digamos então que temos uma função que deve lidar com objetos do tipo
void f(Base* ponteiro_base)
Mas em algum momento a execução deve ser diferente se o objeto passado for
DerivadaA
ou DerivadaB
. Como dynamic_cast
retorna nullptr
em caso de
falha, podemos fazer o seguinte:
void f( Base *ponteiro_base )
{
// tenta cast para ponteiro do tipo DerivadaA
DerivadaA *objA = dynamic_cast<DerivadaA *>( ponteiro_base );
if( objA != nullptr ) {
std::cout << "Objeto do tipo A" << std::endl;
return;
}
// tenta cast para ponteiro do tipo DerivadaB
DerivadaB *objB = dynamic_cast<DerivadaB *>( ponteiro_base );
if( objB != nullptr ) {
std::cout << "Objeto do tipo B" << std::endl;
return;
}
}
int main()
{
DerivadaA A;
DerivadaB B;
Base * ponteiro_base;
ponteiro_base = &A;
f( ponteiro_base );
ponteiro_base = &B;
f( ponteiro_base );
}
Quando usar const_cast
?
Remover a propriedade const
de um objeto é necessidade rara,
e modificar um objeto declarado const
resulta em comportamento indefinido
do programa, então cabe ao programador usar de forma coesa essa conversão.
Um exemplo ilustrativo: Quando deseja-se retornar uma referência a um
membro de uma classe (os famosos setters
e getters
):
class ClasseX {
int X;
public:
// retorna referência a membro da classe
int &getRefX()
{
return X;
};
};
Como a função não altera o estado da classe, posso querer marcá-la
const
:
int& getRefX() const
Mas daí o código não compila (a assinatura const
da função torna seus
membros const
dentro desta)…
error: binding 'const int' to reference of type 'int&' discards qualifiers
const_cast
pode ser usado para retornar a referência não-constante:
//retorna referência a membro da classe
int& getRefX()
{
//(algum comentário explicando o const_cast)
return const_cast<int&>(X);
};
const_cast
deve ser usado com muito cuidado, pois alterar o valor de
uma variável originalmente constante torna o programa mal formado
(undefined behaviour). No exemplo acima, se algum objeto da classe
ClasseX
fosse originalmente constante, a função ainda funcionaria neste,
mas o valor de X
não deveria ser modificado através da referência
retornada.
Quando usar reinterpret_cast
?
O reinterpret_cast
afasta-se da noção de objetos e aproxima-se da
noção de bits (informação).
Por exemplo, digamos que você esteja programando um microcontrolador
e no manual deste diz que a placa de som lê as informações a partir do
endereço 33
na memória:
// endereço obtido do manual do microchip
static const int ADDR_PLACA_DE_SOM = 33;
Para escrever informações nesta área da memória, você precisa converter
esta posição para um ponteiro, com reinterpret_cast
você pode fazê-lo:
// cria ponteiro para inteiros na posição dita pelo manual
int *som_pt = reinterpret_cast<int *>( ADDR_PLACA_DE_SOM );
Note que a conversão é bem livre, você pode criar um ponteiro para qualquer tipo de dado:
// cria ponteiro para chars na posição dita pelo manual
char* som_pt_2 = reinterpret_cast<char*>(ADDR_PLACA_DE_SOM);
C++ também suporta o cast no estilo da linguagem C, como ele é interpretado pelo compilador?
As conversões no estilo-C são do tipo:
(novo_tipo)(expressão)
Um exemplo:
//gera um inteiro através do float 3.14
(int)(3.14f)
Este tipo de conversão está presente no C++ principalmente por questões de compatibilidade com a linguagem C.
O compilador tenta a seguinte ordem de conversões quando um cast no estilo-C é utilizado:
const_cast
.static_cast
, ignorando acesso restrito caso usado em hierarquias de classes.static_cast
seguido deconst_cast
reinterpret_cast
reinterpret_cast
seguido deconst_cast
Ou seja, o compilador tenta quase tudo para gerar um objeto do novo
tipo, inclusive removendo const
. Esse tipo de conversão pode gerar
conversões indesejadas (até o não orientado a objetos
reinterpret_cast
!) que escondem bugs propagáveis pela lógica do programa.
Esta resposta é uma simplificação. Reitero que compreender bem a conversão de objetos indica boa compreensão do paradigma de orientação a objetos. As principais referências foram:
Possible output
Objeto do tipo A
Objeto do tipo B