Alias y definiciones de tipos (C++)

Se puede usar una declaración de alias para declarar un nombre que se usará como sinónimo de un tipo declarado previamente. (Este mecanismo también se conoce informalmente como alias de tipo). También se puede usar para crear una plantilla de alias, que puede resultar útil para los asignadores personalizados.

Sintaxis

using identifier = type;

Comentarios

identifier
Nombre del alias.

type
Identificador de tipo para el que se creará un alias.

Un alias no presenta un tipo nuevo ni puede cambiar el significado de un nombre de tipo existente.

El formato más sencillo de alias es equivalente al mecanismo typedef de C++03:

// C++11
using counter = long;

// C++03 equivalent:
// typedef long counter;

Ambos formatos permiten la creación de variables de tipo counter. Algo más útil sería un alias de tipo como este para std::ios_base::fmtflags:

// C++11
using fmtfl = std::ios_base::fmtflags;

// C++03 equivalent:
// typedef std::ios_base::fmtflags fmtfl;

fmtfl fl_orig = std::cout.flags();
fmtfl fl_hex = (fl_orig & ~std::cout.basefield) | std::cout.showbase | std::cout.hex;
// ...
std::cout.flags(fl_hex);

Los alias también funcionan con punteros de función, pero son mucho más legibles que la definición de tipo equivalente:

// C++11
using func = void(*)(int);

// C++03 equivalent:
// typedef void (*func)(int);

// func can be assigned to a function pointer value
void actual_function(int arg) { /* some code */ }
func fptr = &actual_function;

Una limitación del mecanismo typedef es que no funciona con plantillas. Sin embargo, la sintaxis de alias de tipo de C++11 permite la creación de plantillas de alias:

template<typename T> using ptr = T*;

// the name 'ptr<T>' is now an alias for pointer to T
ptr<int> ptr_int;

Ejemplo

En el ejemplo siguiente se muestra cómo utilizar una plantilla de alias con un asignador personalizado; en este caso, un tipo de vector entero. Puede sustituir cualquier tipo por int para crear un alias adecuado con el fin de ocultar las listas de parámetros complejos en el código funcional principal. El uso del asignador personalizado en todo el código puede mejorar la legibilidad y reducir el riesgo de introducir errores debidos a errores ortográficos.

#include <stdlib.h>
#include <new>

template <typename T> struct MyAlloc {
    typedef T value_type;

    MyAlloc() { }
    template <typename U> MyAlloc(const MyAlloc<U>&) { }

    bool operator==(const MyAlloc&) const { return true; }
    bool operator!=(const MyAlloc&) const { return false; }

    T * allocate(const size_t n) const {
        if (n == 0) {
            return nullptr;
        }

        if (n > static_cast<size_t>(-1) / sizeof(T)) {
            throw std::bad_array_new_length();
        }

        void * const pv = malloc(n * sizeof(T));

        if (!pv) {
            throw std::bad_alloc();
        }

        return static_cast<T *>(pv);
    }

    void deallocate(T * const p, size_t) const {
        free(p);
    }
};

#include <vector>
using MyIntVector = std::vector<int, MyAlloc<int>>;

#include <iostream>

int main ()
{
    MyIntVector foov = { 1701, 1764, 1664 };

    for (auto a: foov) std::cout << a << " ";
    std::cout << "\n";

    return 0;
}
1701 1764 1664

Typedefs

Una declaración typedef introduce un nombre que, dentro de su ámbito, se convierte en un sinónimo del tipo especificado por la parte type-declaration de la declaración.

Puede usar declaraciones de definición de tipo para construir nombres más cortos o significativos para tipos ya definidos por el lenguaje o que ha declarado. Los nombres de typedef permiten encapsular detalles de la implementación que pueden cambiar.

A diferencia de las declaraciones class, struct, union y enum, las declaraciones typedef no introducen tipos nuevos, sino nombres nuevos para tipos ya creados.

Los nombres declarados con typedef ocupan el mismo espacio de nombres que otros identificadores (excepto las etiquetas de instrucciones). Por consiguiente, no pueden usar el mismo identificador que un nombre declarado previamente, excepto en una declaración de tipo de clase. Considere el ejemplo siguiente:

// typedef_names1.cpp
// C2377 expected
typedef unsigned long UL;   // Declare a typedef name, UL.
int UL;                     // C2377: redefined.

Las reglas de nombres ocultos que pertenecen a otros identificadores también controlan la visibilidad de los nombres declarados con typedef. Por consiguiente, el ejemplo siguiente es válido en C++:

// typedef_names2.cpp
typedef unsigned long UL;   // Declare a typedef name, UL
int main()
{
   unsigned int UL;   // Redeclaration hides typedef name
}

// typedef UL back in scope

Otra instancia de ocultación de nombres:

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

Al declarar un identificador de ámbito local con el mismo nombre que un typedef, o al declarar un miembro de una estructura o una unión en el mismo ámbito o en un ámbito interno, se debe indicar el especificador de tipo. Por ejemplo:

typedef char FlagType;
const FlagType x;

Para reutilizar el nombre FlagType para un identificador, un miembro de una estructura o un miembro de una unión, se debe proporcionar el tipo:

const int FlagType;  // Type specifier required

No basta con usar este código:

const FlagType;      // Incomplete specification

El motivo es que se considera que FlagType forma parte del tipo, no que sea un identificador que se volverá a declarar. Esta declaración se considera no válida, de forma parecida al código siguiente:

int;  // Illegal declaration

Es posible declarar cualquier tipo con typedef, incluidos los tipos de puntero, función y matriz. Se puede declarar un nombre de typedef para un puntero a un tipo de estructura o de unión antes de definir el tipo de estructura o de unión, siempre y cuando la definición tenga la misma visibilidad que la declaración.

Ejemplos

Un uso de las declaraciones typedef consiste en hacer que las declaraciones sean más uniformes y compactas. Por ejemplo:

typedef char CHAR;          // Character type.
typedef CHAR * PSTR;        // Pointer to a string (char *).
PSTR strchr( PSTR source, CHAR target );
typedef unsigned long ulong;
ulong ul;     // Equivalent to "unsigned long ul;"

Para usar typedef con el fin de especificar tipos fundamentales y derivados en la misma declaración, se pueden separar los declaradores mediante comas. Por ejemplo:

typedef char CHAR, *PSTR;

El ejemplo siguiente proporciona el tipo DRAWF para una función que no devuelve ningún valor y que toma dos argumentos int:

typedef void DRAWF( int, int );

Después de la instrucción typedef anterior, la declaración

DRAWF box;

sería equivalente a la declaración

void box( int, int );

typedef se suele combinar con struct para declarar y asignar nombres a tipos definidos por el usuario:

// typedef_specifier2.cpp
#include <stdio.h>

typedef struct mystructtag
{
    int   i;
    double f;
} mystruct;

int main()
{
    mystruct ms;
    ms.i = 10;
    ms.f = 0.99;
    printf_s("%d   %f\n", ms.i, ms.f);
}
10   0.990000

Nueva declaración de definiciones de tipos

La declaración typedef se puede usar a fin de volver a declarar el mismo nombre para hacer referencia al mismo tipo. Por ejemplo:

Archivo de origen file1.h:

// file1.h
typedef char CHAR;

Archivo de origen file2.h:

// file2.h
typedef char CHAR;

Archivo de origen prog.cpp:

// prog.cpp
#include "file1.h"
#include "file2.h"   // OK

El programa prog.cpp incluye dos archivos de encabezado que contienen las declaraciones typedef para el nombre CHAR. Mientras las declaraciones hagan referencia al mismo tipo, se puede volver a declarar el nombre.

Un typedef no puede volver a definir un nombre que se haya declarado como otro tipo. Considere la opción de usar esta alternativa file2.h:

// file2.h
typedef int CHAR;     // Error

El compilador genera un error en prog.cpp debido al intento de volver a declarar el nombre CHAR para hacer referencia a otro tipo. Esta directiva se aplica también a las construcciones como la siguiente:

typedef char CHAR;
typedef CHAR CHAR;      // OK: redeclared as same type

typedef union REGS      // OK: name REGS redeclared
{                       //  by typedef name with the
    struct wordregs x;  //  same meaning.
    struct byteregs h;
} REGS;

Definiciones de tipos de C++ frente a C

El uso del especificador typedef con tipos de clase se admite en gran medida debido a la práctica de ANSI C de declarar las estructuras sin nombre en declaraciones typedef. Por ejemplo, muchos programadores de C usan la expresión siguiente:

// typedef_with_class_types1.cpp
// compile with: /c
typedef struct {   // Declare an unnamed structure and give it the
                   // typedef name POINT.
   unsigned x;
   unsigned y;
} POINT;

La ventaja de esta declaración es que hace posibles declaraciones como:

POINT ptOrigin;

en lugar de:

struct point_t ptOrigin;

En C++, la diferencia entre los nombres de typedef y los tipos reales (declarados con las palabras clave class, struct, union y enum) es mayor. Aunque la práctica de C de declarar una estructura anónima en una instrucción typedef todavía funciona, no proporciona ningún beneficio en cuanto a notación, a diferencia de lo que ocurre en C.

// typedef_with_class_types2.cpp
// compile with: /c /W1
typedef struct {
   int POINT();
   unsigned x;
   unsigned y;
} POINT;

En el ejemplo anterior, se declara una clase denominada POINT mediante la sintaxis typedef de la clase sin nombre. POINT se trata como un nombre de clase; sin embargo, a los nombres así especificados se aplican las siguientes restricciones:

  • El nombre (sinónimo) no se pueden mostrar detrás de un prefijo class, struct o union.

  • El nombre no se puede usar como nombre de constructor o destructor dentro de una declaración de clase.

En resumen, esta sintaxis no proporciona ningún mecanismo para la herencia, la construcción o la destrucción.