Condividi tramite


Alias e typedef (C++)

È possibile usare una dichiarazione alias per dichiarare un nome da usare come sinonimo di un tipo dichiarato in precedenza. Questo meccanismo viene anche definito in modo informale come alias di tipo. È anche possibile usare questo meccanismo per creare un modello di alias, che può essere utile per gli allocatori personalizzati.

Sintassi

using identifier = type;

Osservazioni:

identificatore
Nome dell'alias.

type
Identificatore del tipo per cui si sta creando un alias.

Un alias non introduce un nuovo tipo e non può modificare il significato di un nome di tipo esistente.

La forma più semplice di un alias equivale al typedef meccanismo di C++03:

// C++11
using counter = long;

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

Entrambi questi moduli consentono la creazione di variabili di tipo counter. Un'operazione particolarmente utile è un alias di tipo come questo per 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);

Gli alias funzionano anche con i puntatori a funzione, ma sono molto più leggibili rispetto al typedef 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 limitazione del typedef meccanismo è che non funziona con i modelli. Tuttavia, la sintassi di alias di tipo in C++11 consente la creazione di modelli di alias:

template<typename T> using ptr = T*;

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

Esempio

L'esempio seguente illustra come usare un modello di alias con un allocatore personalizzato, in questo caso un tipo intero vettoriale. È possibile sostituire qualsiasi tipo per int creare un alias pratico per nascondere gli elenchi di parametri complessi nel codice funzionale principale. Usando l'allocatore personalizzato in tutto il codice, è possibile migliorare la leggibilità e ridurre il rischio di introdurre bug causati da errori di digitazione.

#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

Typedef

Una typedef dichiarazione introduce un nome che, all'interno dell'ambito, diventa un sinonimo del tipo specificato dalla parte di dichiarazione di tipo della dichiarazione .

È possibile usare le dichiarazioni typedef per costruire nomi più brevi o più significativi per i tipi già definiti dal linguaggio o per i tipi dichiarati. I nomi di typedef consentono di incapsulare dettagli di implementazione che possono cambiare.

A differenza delle classdichiarazioni , struct, unione enum , typedef le dichiarazioni non introducono nuovi tipi, ma introducono nuovi nomi per i tipi esistenti.

I nomi dichiarati usando typedef occupano lo stesso spazio dei nomi degli altri identificatori (ad eccezione delle etichette delle istruzioni). Pertanto, non possono usare lo stesso identificatore di un nome dichiarato in precedenza, tranne in una dichiarazione di tipo classe. Si consideri l'esempio seguente:

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

Le regole di nascondere il nome relative ad altri identificatori regolano anche la visibilità dei nomi dichiarati tramite typedef. Di conseguenza, l'esempio seguente è valido in 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

Un'altra istanza del nome nascosta:

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

Quando si dichiara un identificatore di ambito locale con lo stesso nome di un typedefoggetto o quando si dichiara un membro di una struttura o di un'unione nello stesso ambito o in un ambito interno, è necessario specificare l'identificatore di tipo. Ad esempio:

typedef char FlagType;
const FlagType x;

Per riutilizzare il nome FlagType per un identificatore, un membro della struttura o un membro dell'unione, deve essere fornito il tipo:

const int FlagType;  // Type specifier required

Non è sufficiente dire

const FlagType;      // Incomplete specification

poiché l'oggetto FlagType viene preso come parte del tipo, non un identificatore che viene dichiarato di nuovo. Questa dichiarazione viene considerata come una dichiarazione non valida, simile alla seguente:

int;  // Illegal declaration

È possibile dichiarare qualsiasi tipo con typedef, inclusi i tipi puntatore, funzione e matrice. È possibile dichiarare un nome di typedef per un puntatore a un tipo di unione o di struttura prima di definire il tipo di struttura o di unione, purché la definizione abbia la stessa visibilità della dichiarazione.

Esempi

Un uso delle typedef dichiarazioni consiste nel rendere le dichiarazioni più uniformi e compattate. Ad esempio:

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;"

Per usare typedef per specificare tipi fondamentali e derivati nella stessa dichiarazione, è possibile separare i dichiaratori con virgole. Ad esempio:

typedef char CHAR, *PSTR;

L'esempio seguente fornisce il tipo DRAWF per una funzione che non restituisce alcun valore e accetta due argomenti int:

typedef void DRAWF( int, int );

Dopo l'istruzione precedente typedef , la dichiarazione

DRAWF box;

sarà equivalente alla dichiarazione

void box( int, int );

typedef viene spesso combinato con struct per dichiarare e assegnare un nome ai tipi definiti dall'utente:

// 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

Redeclaration of typedefs

La typedef dichiarazione può essere usata per ripetere lo stesso nome per fare riferimento allo stesso tipo. Ad esempio:

file1.hFile di origine :

// file1.h
typedef char CHAR;

file2.hFile di origine :

// file2.h
typedef char CHAR;

prog.cppFile di origine :

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

Il file prog.cpp include due file di intestazione, entrambi contenenti typedef dichiarazioni per il nome CHAR. Se entrambe le dichiarazioni si riferiscono allo stesso tipo, tale ridichiarazione è accettabile.

Un typedef oggetto non può ridefinire un nome dichiarato in precedenza come tipo diverso. Si consideri questa alternativa file2.h:

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

Il compilatore genera un errore in prog.cpp a causa del tentativo di ripetere il nome CHAR per fare riferimento a un tipo diverso. Questo criterio si estende a costrutti come:

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;

typedef in C++ e C

L'uso dell'identificatore typedef con i tipi di classe è supportato in gran parte a causa della pratica ANSI C di dichiarare strutture senza nome nelle typedef dichiarazioni. Ad esempio, molti programmatori C usano il linguaggio seguente:

// 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;

Il vantaggio di questo tipo di dichiarazione è che consente dichiarazioni quali:

POINT ptOrigin;

invece di:

struct point_t ptOrigin;

In C++, la differenza tra typedef nomi e tipi reali (dichiarati con le classparole chiave , structunion, e enum ) è più distinta. Anche se la pratica C di dichiarare una struttura senza nome in un'istruzione typedef funziona ancora, non offre vantaggi di notazione così come in C.

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

Nell'esempio precedente viene dichiarata una classe denominata POINT usando la sintassi della classe typedef senza nome. POINT viene considerato come un nome di classe; tuttavia, le restrizioni seguenti vengono applicate ai nomi introdotti nel modo seguente:

  • Il nome (sinonimo) non può essere visualizzato dopo un classprefisso , structo union .

  • Il nome non può essere usato come nome di costruttore o distruttore all'interno di una dichiarazione di classe.

In sintesi, questa sintassi non fornisce alcun meccanismo per l'ereditarietà, la costruzione o la distruzione.