Não sei quanto a você, mas eu já topei com alguns castings que encheram a paciência. Vejam: Eu tenho a mania de criar typedefs para todo tipo complexo (estruturas e arrays, por exemplo), para lidar com eles como um tipo simples. Para isso temos que levar algumas coisas em consideração…
Sempre que você faz uma declaração assim:
float vector[4];
Você está declarando um ponteiro chamado vector que aponta para uma sequência de 4 floats. Com a minha mania a coisa fica mais ou menos assim:
typedef float vector4_t[4]; vector4_t vector;
Que é a mesma coisa! Acabamos de declarar um ponteiro vector que aponta para um item do tipo vector4_t. Sei que é um ponteiro porque o tipo é um array de 4 floats, igualzinho ao primeiro exemplo. Agora vejam isso:
typedef float vector4_t[4]; typedef float plane_t[4]; typedef plane_t frustum_t[6]; extern int PlaneDistance(plane_t, vector4_t, float); int FrustumSphereTest(frustum_t fr, vector4_t center, float radius) { int i; for (i = 0; i < 6; i++) if (PlaneDistance((plane_t)fr[i], center, radius) <= 0) return 0; return 1; }
Este fragmento de código causa um erro de compilação, justamente na linha onde PlaneDistance() é chamada. “error: cast specifies array type”. Que coisa doida! O tipo frustum_t é justamente definido como sendo um array de plane_t‘s e a função toma um tipo plane_t como parâmetro!
Well… coisas do compilador… mas eu vou mostrar um macete (meio perigoso) que sempre funciona… É, em essência, o mesmo macete usado neste artigo: Se substituirmos a linha do if que contém a chamada para PlaneDistance() por isso:
if (PlaneDistance(*&fr[i], center, radius) <= 0)
O erro desaparece! Tudo o que fiz foi obter um ponteiro e depois derrefernciá-lo. Veja que fr[i] é do tipo plane_t, mas é definido como um array de 4 floats. Se eu pego o endereço do item do array e depois pego o dado apontado, é como se eu estivesse passando fr[i] sem incomodar o compilador!! O macete de pegar o ponteiro e depois derreferenciá-lo serve para enganar o esquema de promoção de tipos de C.
Particularmente, acho o sistema de casting de C bem melhor do que o implementado em C++. Este último tem 3 tipos diferentes de casting (pelo menos): static, dynamic e const. A sintaxe também não me agrada muito… tem gente que gosta:
int *x = 10; float *y; y = static_cast<float *>(x); /* Estilo C++ */ y = (float *)x; /* Estilo C */
Eis um crash course nos tipos de casting de C++:
- static_cast é a mesma coisa do estilo de casting do C;
- dynamic_cast é um casting que pode devolver NULL se a promoção de tipo for inválida;
- const_cast é uma violação das regras do modificador const. Transforma um tipo const num tipo não-const.
O dynamic_cast, na minha opinião, é coisa para preguiçosos… A idéia é que você não poderia poder converter ponteiros entre duas classes que não têm classes base em comum. E o const casting, para mim, é idiota, já que, se um objeto é definido como const, então o foi por uma boa causa (isto é, o desenvolvedor não quer que ele seja modificado!).
Voltando ao “macete” que mostrei aqui. Tome cuidado quando for usá-lo com tipos diferentes, usando casting, por exemplo:
typedef float vector3_t[3];
typedef float vector4_t[4];
extern int Vector4BoxTest(plane_t, vector4_t);
plane_t p;
vector3_t x;
if (Vector3BoxTest(p, *(vector4_t *)&x) < 0)
...
Aqui a função espera por um tipo vector4_t, mas estamos passando uma variável do tipo vector3_t. Como a variável x tem um float a menos, é possível obter um Segmentation Fault na chamada (quando a rotina tentar acessar o quarto float). Pior ainda: Se tivessemos outra variável declarada depois do x, um int, por exemplo, é provável que o compiador gerasse código que acessasse o conteúdo desse int como se fosse o quarto float, sem causar nenhum erro de compilação!
O compilador não reclamará com o if acima, já que convertemos o ponteiro para vector3_t para um do tipo vector4_t, mas isso pode ser perigoso… Antes de usar esse macete, entenda o que você está fazendo, ok?
Shame on me…
Ontem mesmo me deparei com treco destes, tentando resolver um problema de modo rapido.
Faço o seguinte – solução de contorno e altamente deselegante – declaro um ponteiro para o array e passo com casting void * !
Resolve sempre, mas é perigoso!
Dai quanto tenho tempo conserto, mas confesso que não gosto de ficar criando typedefs…
Outra técnica que eu acho interessante, com relação aos typedefs é declarar também o tipo “ponteiro” para o tipo definido no typedef. A primeira vez que vi isso foi no header windows.h (pelo menos eles servem pra algo!):
Assim, se eu precisar de um ponteiro para a estrutura, uso o tipo vector3_p, se precisar do tipo da estrutura, uso vector3_s e, se precisar declarar a estrutura ao estilo de C, no caso, por exemplo, de ter uma lista encadeada, uso struct _vector3_s.
Esse estilo de nomenclatura para tipos não é o usado pela Microsoft, que prefere adotar símbolos em maiúsculos… Essa nomenclatura eu vi em códigos feitos para *nix. Adotei-a… coloco sufixos _s para estruturas, _t para “tipos”, _p para “ponteiros”, _e para enums, _u para unions.
É claro que tenho a tendência a usar apenas _t e _p (porque o nome do tipo me dá uma dica do seu conteúdo), mas para projetos mais complicados, onde ficar rastreando os tipos é uma complicação, uso o que falei acima.