Typedef arcaico

2010/04/20

A API do Windows geralmente prima pela excelência em maus exemplos. A Notação Húngara e o Typedef Arcaico são duas técnicas que, por motivos históricos, são usados a torto e a direito pelos códigos de exemplo.

Já foi escrito muita coisa sobre os prós e contras da notação húngara. Já o typedef arcaico, esse pedacinho imprestável de código, ficou esquecido, e hoje em dia traz mais dúvidas na cabeça dos principiantes em C++ do que deveria. Para tentar desobscurecer os mitos e fatos, vamos tentar explicar o que significa essa construção tão atípica, mas comum no dia-a-dia.

Vejamos um exemplo típico desse pequeno Frankenstein semântico:

typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;

Bom, eu nem sei por onde começar. Talvez pelo conceito de typedef.

Typedefs

Um typedef, basicamente, é um apelido. Você informa um tipo e define “outro tipo”.

typedef <tipo> apelido;

O é tudo que fica entre o typedef e o novo nome, que deve ser um identificador válido na linguagem. Por exemplo, a empresa onde trabalho fez um typedef informal do meu nome:

typedef Wanderley Caloni Wandeco;

Se, futuramente, eu sair da empresa e entrar outro “Wanderley alguma-coisa”, será possível usar o apelido novamente, bastando alterar o typedef:

typedef Wanderley Cardoso Wandeco;
_Bom, "outro tipo" é forma de dizer. Isso é uma descrição errônea em muitos livros. De fato, o compilador enxerga **o mesmo tipo com outro nome**, daí chamarmos o typedef de apelido, mesmo._
```cpp /** @file dois_apelidos.cpp */ #include using namespace std; struct Struct { int x; int y; }; typedef Struct Struct1; typedef Struct Struct2; int main() { Struct1 s1; Struct2 s2; cout << typeid(s1).name() << endl; cout << typeid(s2).name() << endl; } ```
> > C:\Tests>cl /EHsc dois_apelidos.cpp > ... > /out:dois_apelidos.exe > dois_apelidos.obj > > C:\Tests>dois_apelidos.exe > > struct Struct struct Struct

Granularidade dos tipos

Tipos simples são fáceis de entender porque possuem seus símbolos no mesmo lugar:

int

x;

char

c;

long

p;

Já os tipos um pouco mais complicados permite alguma mudança aqui e acolá:

int*

 x;

char *

y;

long *

 p;

Essa liberdade da linguagem, mesmo sendo um recurso útil, pode ser bem nocivo dependendo de quem olha o código:

int x, y; // dois inteiros
int * x, y; // um ponteiro para inteiro e um inteiro
int x, *y; // um inteiro e um ponteiro para inteiro
int *x, y; // um ponteiro para inteiro e um inteiro

Em algumas formas da sintaxe, além de ser inevitável, gera bastante desconfiança:

// Um ponteiro para função que recebe dois inteiros e não retorna nada.
typedef

void (*

FP

)(int, int)

;

// Um ponteiro para função que recebe dois inteiros e não retorna nada.
void (*)(int, int);

// Um cast para ponteiro para função que recebe dois inteiros e não retorna nada.
( (

void (*)(int, int)

 ) pf )(x, y);
#include <iostream>

void func(int x, int y)
{
   std::cout << x << '-' << y << '\n';
}

int main()
{
   void* pf = func;
   ( ( void (*)(int, int) ) pf )(3, 14);
}
 

Structs em C++

Antigamente, as structs eram construções em C que definiam um agregado de tipos primitivos (ou outras structs) e que poderiam gerar variáveis desse tipo em qualquer lugar, desde que informado seu nome e que se tratasse de uma struct:

/** @file structs.cpp */
struct MyStruct { int x, y; };

void func1()
{
   struct MyStruct ms;
   //...
}

void func2(struct MyStruct msa)
{
   //...
}

int main()
{
   struct MyStruct ms;
   func2(ms);
}
 

Para evitar toda essa digitação, os programadores usavam um pequeno truque criando um apelido para a estrutura, e usavam o apelido no lugar da struct (apesar de ambas representarem a mesma coisa).

struct MyStruct

 { int x, y; };
typedef

struct MyStruct

 MS;

ou

typedef struct MyStruct { int x, y; } MS;

struct MyStruct

 ms1; // ainda prolixo

MS

 ms2; // mais simples

Com a definição da linguagem C++ padrão, e mais moderna, essa antiguidade foi removida, apesar de ainda suportada. Era possível usar apenas o nome do struct como seu tipo:

/** @file structs.cpp */
struct MyStruct { int x, y; };

void func1()
{
   /*struct*/ MyStruct ms;
   //...
}

void func2(/*struct*/ MyStruct msa)
{
   //...
}

int main()
{
   /*struct*/ MyStruct ms;
   func2(ms);
}
 

Porém, isso vai um pouco além de quando a Microsoft começou a fazer código para seu sistema operacional. Naquela época, o padrão ainda estava se formando e existia mais ou menos um consenso de como seria a linguagem C++ (sem muitas alterações do que de fato a linguagem C já era). De qualquer forma, a linguagem C imperava bem mais que C++. Dessa forma, já era bem formada a ideia de como declarar uma struct: a forma antiga.

typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;

Além do uso controverso do** _sublinhado** para nomear entidades (que no padrão foi recomendado que se reservasse aos nomes internos da biblioteca-padrão) e do uso de MAÍUSCULAS_NO_NOME (historicamente atribuído a nomes definidos no pré-processador), o uso do typedef atracado a um struct era muito difundido. E ficou ainda mais depois que a API do Windows foi publicada com essas definições.

Como fazer,então?

Ora, do mesmo jeito que é feito há vinte anos: sem typedefs. O próprio paradigma da linguagem, independente de padrões de APIs, de sistemas operacionais ou de projetos específicos já orienta o programador para entender o que o espera na leitura de um código-fonte qualquer. Qualquer pessoa que aprendeu o básico do básico sobre ponteiros e structs consegue ler o código abaixo:

// Papai, o que que é isso?
// Ora, filho, apenas uma definição de estrutura!
//
struct MinhaStruct {
   int x;
   int y;
};

// muitas linhas abaixo...

void func(MinhaStruct* ms)
{
   // asterisco significa ponteiro para MinhaStruct!
}

int main()
{
   MinhaStruct ms;
   func(&ms);
}
 

Agora, para entender a forma antiga, ou você se baseou no copy&paste dos modelos Microsoftianos, ou seja, decoreba, ou você é PhD em Linguagem C/C++ e padrões históricos de linguagens legadas. Se não é, deveria começar o curso agora.

// Papai, o que que é isso?
// Ora, filho, apenas uma definição de sinônimo da struct
// _MINHASTRUCT, cujo nome não é usado, para dois nomes
// em maiúsculas, apesar se não serem defines, com uma
// nomenclatura de ponteiro que eu nunca vi na vida (obs: 
// papai programa em um sistema não-Windows).
//
typedef struct _MINHASTRUCT {
   int x;
   int y;
}
MINHASTRUCT, *LPMINHASTRUCT;

// muitas linhas abaixo...

void func(LPMINHASTRUCT ms)
{
   // o que diabos é um LP, mesmo?
}

int main()
{
   MINHASTRUCT ms;
   func(&ms);
}
 

Código Antes x Depois no Visual StudioDa mesma forma, o uso de uma estrutura simples de tipos mantém a lista de nomes do seu projeto limpa e clara. Compare o visualizador de classes em projetos Windows com algo mais C++ para ter uma ideia.

É claro, essa é apenas uma sugestão. Existem vantagens em sua utilização. Existe alguma vantagem no modo antigo? Existe: a Microsoft usa, e talvez mais pessoas usem. Basta a você decidir qual deve ser o melhor caminho.

Atualização

De acordo com o leitor  Adriano dos Santos Fernandes, a obrigatoriedade do nome struct após seu nome continua valendo para a linguagem C padrão, assim como no compilador GCC ocorre um erro ao tentar omiti-la. Apenas na linguagem C++ essa obrigatoriedade não existe mais.

Eu não fiz meus testes, mas confio no diagnóstico de nosso amigo. A maior falha do artigo, no entanto, é usar a linguagem C como base, quando na verdade ele deveria falar sobre o uso desses typedefs em C++. Esse erro também foi corrigido no original.

Facebook | Twitter | Linkedin | Google