3.2.1a typedef
§1 Sinopsis
La palabra reservada typedef se utiliza para asignar un alias (otro nombre) a un tipo. No crea ningún nuevo tipo, solo define un nuevo identificador (type-id 2.2) para un tipo que ya tiene su propio identificador (el identificador puede ser un nombre o una expresión compleja que contiene al nombre). Es importante recalcar que el nuevo nombre es un añadido, y no sustituye al identificador original. Ambos identificadores pueden ser intercambiados libremente en cualquier expresión.
Formalmente typedef es un especificador de identificador (nombre). Su introducción en el lenguaje se debe a que, como reconoce su propio creador [1], la sintaxis de declaraciones C++ es innecesariamente dura de leer y escribir. Esto se debe a la herencia del C y a que la notación no es lineal, sino que mimetiza la sintaxis de las expresiones que está basada en precedencias. En este sentido, la utilización de typedef permite paliar en parte el problema, mejora la legibilidad y ayuda a la documentación y mantenimiento del programa, ya que permite sustituir identificadores de tipo complejos por expresiones más sencillas.
§2 Sintaxis:
typedef <tipo> <alias>;
Asigna un nombre <alias> con el tipo de dato de <tipo>.
Nota: para distinguir un identificador <alias> introducido con un typedef de un nombre de tipo normal <tipo>, al primero se le denomina nombre-typedef ("typedef-name") o alias-typedef ("typedef-alias").
Los typedef pueden ser usados con tipos simples o abstractos, pero no con nombres de funciones.
§3 Ejemplos:
typedef unsigned char BYTE;
en lo sucesivo, cualquier referencia a BYTE (es una tradición de C/C++ utilizar los alias-typedef en mayúsculas) equivale a colocar en su lugar unsigned char, incluso para crear nuevos tipos unsigned char:
BYTE z, y // equivale a: unsigned char z, y
...
typedef const float KF;
typedef const float* KF_PTR;
KF pi = 3.14;
KF_PTR ppi = π
typedef long clock_t; // no sería muy C++, mejor CLOCK_T
clock_t slice = clock();
§3.1 En realidad el nuevo identificador introducido por typedef se comporta
dentro de su ámbito como una nueva palabra-clave con la que pueden declararse nuevos tipos:
typedef int* Pint;
...
Pint p1, p2; // Ok. p1 y p2 son punteros-a-int
También pueden encadenarse, de forma que pueden definirse nuevos alias en función de otros definidos previamente. Por ejemplo:
typedef char CHAR;
typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR ;
§3.2 Como en otras declaraciones, es posible definir una serie de alias-typedef mediante expresiones unidas por
comas. Por ejemplo:
typedef const char * LPCCH, * PCCH, * LPCSTR, * PCSTR ;
...
LPCCH ptr1 = "Hola América";
PCSTR ptr2 = "Hola América";
const char* ptr3 = "Hola América";
Los punteros ptr1; ptr2 y ptr3 son equivalentes.
Otros ejemplos (tomados de definiciones reales de MS VC++)
typedef long INT_PTR, *PINT_PTR;
typedef unsigned short UHALF_PTR, *PUHALF_PTR;
typedef short HALF_PTR, *PHALF_PTR;
§3.3 Otros ejemplos de typedef utilizados frecuentemente en la programación
Windows (
Ejemplos):
§4 Asignaciones complejas
Es útil utilizar typedef para asignaciones complejas; en particular en la declaración y definición de punteros a funciones. Por ejemplo:
typedef long (*(*(*FPTR)())[5])(); // L.1
FPTR an; // L.2
La sentencia L.1 define un identificador: FPTR como un puntero a función que no recibe argumentos y devuelve un puntero a array de 5 punteros a función, que no reciben ningún parámetro y devuelven long. Después, L.2 declara que an es un elemento del tipo indicado.
typedef void (new * new_handler)(); // L.3
new_handler set_new_handler(new_handler); // L.4
L:3 define new_handler como el identificador de un puntero a función que no recibe argumentos y devuelve void [2]. Después L.4 declara set_new_handler como el nombre de una función que devuelve el tipo new_handler recién definido y acepta un único argumento de este mismo tipo.
typedef void (X::*PMF)(int); // L.5
PMF pf = &X::func; // L.6
L.5 define el identificador PMF como puntero a función-miembro de la clase X que recibe un int y no devuelve nada. L.6 declara pf como tipo PMF (puntero a función...) que apunta al método func de X.
§4.1 Cuando se trata de expresiones muy complejas, puede ser útil proceder por fases, aprovechando la propiedad
ya enunciada (§3.1 ) de que pueden
definirse nuevos alias en función de otros definidos previamente . Por ejemplo, queremos definir un puntero p a
matriz de 5 punteros a función que toman un entero como argumento y devuelven un puntero a carácter. En este caso, puede ser
conveniente empezar por las funciones:
char* foo(int); // foo es una función aceptando int y devolviendo char
typedef char* F(int); // F es un alias de función aceptando int y devolviendo...
a continuación definimos la matriz:
F* m[5]; // m es una matriz de 5 punteros a F
typedef F* M[5]; // M es el alias de una matriz de 5 punteros a F
Finalmente escribimos la declaración del tipo solicitado:
M* p;
Cualquier manipulación posterior del tipo mencionado resulta ahora mucho más sencilla. Por ejemplo, la declaración:
int* foo(M*);
corresponde a una función aceptando un puntero a matriz... que devuelve un puntero a int.
§4.2 En ocasiones, la simplificación mencionada el en
párrafo anterior, permite solventar algún que otro problema con la
compilación de expresiones complejas. Por ejemplo, un miembro de cierta
clase tiene la siguiente declaración:
class MYClass {
...
MyNAMESPACE::Garray<MyNAMESPACE::MyString> arrQuer;
...
}
Como sugiere su nombre, Garray es una clase genérica, destinada a almacenar matrices de objetos de cualquier tipo, está definida en el espacio de nombres MyNAMESPACE. En este caso, el objeto arrQuer es una matriz de objetos tipo MyString, que es una clase para manejar cadenas de caracteres -similar a la conocida string de la librería estándar de plantillas STL- también definida en MyNAMESPACE. En resumen, arrQuer es una matriz de cadenas de caracteres.
Ocurre que deseo incluir una invocación explícita para la destrucción del objeto arrQuer en el destructor de MYClass, Algo como:
class MYClass {
...
MyNAMESPACE::Garray<MyNAMESPACE::MyString> arrQuer;
...
~MYClass() {
arrQuer.~MyNAMESPACE::Garray<MyNAMESPACE::MyString>(); // Compiler error
}
}
Sin embargo, el compilador [3] muestra un mensaje de error porque no es capaz de interpretar correctamente la sentencia señalada. En este caso, la utilización de un typedef resuelve el problema y el compilador construye sin dificultad la aplicación.
class MYClass {
...
typedef MyNAMESPACE::Garray<MyNAMESPACE::MyString> ZSTR;
ZSTR arrQuer;
...
~MYClass() {
arrQuer.~ZSTR(); // Compilación Ok.
}
}
&5 También es posible utilizar typedef al mismo tiempo que se declara una estructura u otro tipo de clase. Ejemplo:
typedef {
double re, im;
} COMPLEX;
...
COMPLEX c, *ptrc, Arrc[10];
La anterior corresponde a la definición de una estructura anónima que puede ser utilizada a través de su typedef. Por
supuesto, aunque no es usual necesitar el nombre y el alias simultáneamente, también se puede poner nombre a la estructura al mismo tiempo que se declara el typedef. Por ejemplo:
typedef struct C1 {
double re, im;
} COMPLEX;
...
C1 c, *ptrc;
COMPLEX Arrc[10];
Otro ejemplo tomado de una definición real: ( 4.5.5c)
&6 Otro uso de typedef puede consistir en localizar determinadas referencias concretas en un solo
punto (donde se declara el typedef), de forma que los posibles cambios posteriores solo requieren cambiar una línea de
código ( en mi opinión, quizás sea esta la razón más
importante para la utilización de este especificador). Por ejemplo, supongamos que necesitamos una variable de 32 bits y estamos
utilizando C++Builder, en el que int tiene justamente 32 bits
( 2.2.4); a pesar de ello utilizamos
la expresión:
typedef int INT32;
en lo sucesivo, para todas las referencias utilizamos INT32 en vez de int. Por ejemplo:
INT32 x, y, z;
Si tuviésemos que portar el código a una máquina o a un compilador en el que int fuese menor y, por ejemplo, los 32 bits exigieran un long int, solo tendríamos que cambiar la línea de código del typedef:
typedef long INT32; // Todas las demás referencias se mantienen
...
INT32 x, y, z; // Ok.
§7 No está permitido usar typedef con clases de declaración adelantada
(
4.11.4). Por ejemplo sería incorrecto:
typedef struct COMPLEX; // Ilegal!!
Tampoco está permitido utilizarlo en la definición de funciones o utilizar dos veces el mismo identificador en el mismo espacio de nombres:
typedef long INT32;
...
typedef float INT32; // Error!!
Cuando el alias-typedef se refiere a una clase, se constituye en un nombre de
clase. Este alias no puede ser utilizado después de los prefijos class,
struct o union. Tampoco puede ser utilizado con los
nombres de constructores o destructores dentro de la clase. Ejemplo:
typedef struct S {
...
S(); // constructor
~S(); // destructor
} STR;
...
S o1 = STR(); // Ok.
STR o2 = STR(); // Ok.
struct STR* sp; // Error!!
Observe que:
typedef struct {
...
S(); // Error!!
~S(); // Error!!
} STR;
...
S() y ~S() serían tratadas como funciones normales, no como constructor y destructor de la estructura.
§8 Cuando se quiere utilizar un alias para el identificador de un espacio de nombres, el mecanismo es
distinto; no es necesario utilizar typedef. Ver: Alias de subespacios
(
4.1.11a).
[1] Stroustrup & Ellis: ACRM §8.2
[2] Se trata precisamente de la descripción del manejador de errores del operador new, contenido en <new.h> ( 4.9.20d).
[3] En este caso se ha utilizado el compilador Microsoft Visual Studio 2008 Version 9.0.21022.8