Usando o static cast

De ccppbrasil.org

Usando o static_cast

O operador static_cast faz a conversão entre tipos relacionados, por exemplo de um ponteiro para outro, faz a conversão de uma enumeração para um tipo integral ou de um tipo de ponto flutuante para um tipo integral. Vamos examinar cada caso:

1) Conversão entre ponteiros/referências

class base {};
class derived : public base {};

void f() {
  derived d;  
  base *ptr_base = static_cast<base*>(&d); 
  //...
}

É possível colocar o static_cast para fazer a conversão de um ponteiro para da classe derivada para a classe base. No entanto não existe nenhum problema em se deixar o código como segue abaixo:

...
derived d;
base *ptr_base = &d; 
...

Pois o compilador irá fazer a mesma checagem.

2) Conversão entre enumerações e tipo integrais
Exemplo

enum Enum { a, b, c , d = 2147483647 };
...
int i = a;
...

Lembrando que uma enumeração é um tipo integral de um tamanho que caiba seu maior elemento. A conversão de "Enum" para um tipo integral é aceitavél naturalmente sendo o "Enum" um tamanho menor ou igual ao tipo integral. É preciso tomar cuidado em uma situação como a seguinte:

...
char c = d;
...

Pois o enum pode estar contendo um valor maior que o tamanho do char (Este tipo de verificação poderia ser feita em tempo de compilação com asserts estáticos) Se você tem certeza que o valor cabe em um char, você pode usar um static_cast para dizer ao compilador: "eu sei o que estou fazendo".

char c = static_cast<char>(d);

Conversão de um tipo integral para um enum. Esta conversão é naturalmente errada, pois não existe garantia que o tipo integral contenha um valor válido no enum. Neste caso é preciso colocar um static_cast pois o compilador nem sequer aceita a conversão Evite este tipo de construção. Se for preciso, coloque uma comentário com a justificativa.

int i = 2;
Enum e = i; // não compila
Enum e = static_cast<Enum>( i ); // ok, eu sei que o valor 2 é valido para o enum porque ...

3) Conversão entre tipos de ponto flutuante e tipos integrais
Exemplo:

int i = 3;
double d = i; 

Esta conversão é natural e não apresenta grandes problemas. É possivel utilizar um static_cast mas ele não traz muitos benefícios.
Segundo exemplo:

double d = 3.4;
int i = static_cast<int>(d);

Nesta conversão ocorre com trucamento. Não é obrigatório o uso do static_cast. Mas é recomendado pois fica mais claro que o programador tem conciência que vai haver truncamento no cast. Para casts numéricos com verificação de intervalos veja: http://www.boost.org/libs/numeric/conversion/doc/numeric_cast.html

Agora examinando melhor o caso dos ponteiros. O static_cast também pode ser usado aonde seja clara que a conversão entre ponteiros pode ser feita.
Por exemplo:

void f(int i)
{
  void * ptr = &i;
  f2(static_cast<int*>(ptr));// declarada como: void f2(int * p);
}

Se a mesma função fosse definida assim:

void f(void *ptr) {
  f2(static_cast<int*>(ptr));// declarada como: void f2(int * p);
}

Não existe nenhuma garantia na conversão acima. Este tipo de construção deve ser evitada, mas se por algum motivo seja necessária então neste caso é melhor utilizar um reinterpret_cast. O static_cast também pode ser usado, mas por uma questão de clareza é interessante colocar um reinterpret_cast. Não vou detalhar o reinterpret_cast aqui, mas basicamente o reinterpret_cast é usado em último caso forçando uma conversão que o programador deve garantir. Eu reitero, que este tipo de contrução pode e deve ser evitada.

void f(void *ptr) {
  //OBS: eu tenho que fazer este cast porque....
  f2(reinterpret_cast<int*>(ptr));// declarada como: void f2(int * p);
}

Convertento ponteiros da classe base para a classe derivada

void () {
  derived d;
  base * pb = &d;
  derived * pd = static_cast<derived*> (pb); // ok, neste contexto esta garantido
}

Agora outro exemplo

void (base * base) {
  derived * pd = static_cast<derived*>(base);
}

Neste exemplo não existe garantia nenhuma que o cast é possível. Não é recomendado usar o static_cast nesta situação. Este tipo de código deve ser evitado. Para este tipo de cast é recomendado o dynamic_cast. Se por algum motivo este código fosse usado ele deveria ter um comentário justificando. Abaixo um exemplo válido usando dynamic_cast

void (base * base) {
  derived * pd = dynamic_cast<derived*>(base);
  if (pd != 0)
  {
    // base precisa ser um tipo polimórfico
    // ok, cast válido
  }
}

Usando static_cast para remover ambiguidades
Veja o exemplo abaixo:

class A {};

class B : public A {};

class C : public A {};

class D : public C, public B {};

...
D d;
A * pA = static_cast<B*>(&d);
...

A classe D pode ser convertida na classe A usando a herança da classe B ou da classe C. Neste caso é obrigatório usar o cast para remover a ambiguidade.

Resumindo (conselhos)

[1] Use static_cast sempre que fizer uma conversão de um tipo integral para um enum. Se a validade da conversão não estiver clara coloque uma justificativa.

[2] Use static_cast para reforçar a idéia que um valor do enum cabe em um tipo integral que pode ser menor que o enum. Caso a conversão seja clara não coloque o static_cast.

[3] Use static_cast para converter um tipo de ponto flutuante para um tipo integral. Isso deixa claro que o valor pode ser truncado e que você tem conciência disto.

[4] Use static_cast para fazer casts de ponteiros relacionados dentro de um contexto que fique garantida a conversão.

[5] Não use static_cast para fazer conversão de um ponteiro de uma classe derivada para uma classe base. Não é necessário a não ser que exista alguma ambiguidade.

[6] Só use static_cast para converter um ponteiro da classe base para a classe derivada em um contexto em que a conversão esteja garantida. Se você não puder garantir altere sua função. Caso você precise deste comportamento utilize um dynamic_cast. Em último caso coloque uma boa justificafica da garantia do cast.

[7] Evite o uso do reinterpret_cast

[8] Não use o cast estilo C no C++.

[9] Lembre-se que muitos casts em seu programa pode indicar um design incorreto.



Referências
[Stroustrup, 2000] B. Stroustrup: “The C++ Programming Language (Special Edition)”. Addison Wesley. Reading Mass. USA. February 2000. ISBN 0-201-70073-5.

Ferramentas pessoais