Alias et typedefs (C++)

Vous pouvez utiliser une déclaration d’alias pour déclarer un nom à utiliser comme synonyme d’un type précédemment déclaré. (Ce mécanisme est également appelé un alias de type de manière informelle). Vous pouvez également utiliser ce mécanisme pour créer un modèle d’alias, qui peut être utile pour les allocateurs personnalisés.

Syntaxe

using identifier = type;

Notes

identifier
Nom de l'alias.

type
Identificateur de type pour lequel vous créez un alias.

Un alias n’introduit pas de nouveau type et ne peut pas changer la signification d’un nom de type existant.

La forme la plus simple d’un alias est équivalente au typedef mécanisme de C++03 :

// C++11
using counter = long;

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

Ces deux formulaires permettent la création de variables de type counter. Un élément plus utile est un alias de type comme celui-ci pour 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);

Les alias fonctionnent également avec des pointeurs de fonction, mais sont beaucoup plus lisibles que le typedef équivalent :

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

Une limitation du typedef mécanisme est qu’il ne fonctionne pas avec des modèles. Toutefois, la syntaxe de l'alias de type en C++11 permet la création de modèles d'alias :

template<typename T> using ptr = T*;

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

Exemple

L'exemple suivant montre comment utiliser un modèle d'alias avec un allocateur personnalisé, dans ce cas un type de vecteur entier. Vous pouvez remplacer n’importe quel type pour int créer un alias pratique pour masquer les listes de paramètres complexes dans votre code fonctionnel principal. En utilisant l’allocateur personnalisé tout au long de votre code, vous pouvez améliorer la lisibilité et réduire le risque d’introduire des bogues causés par des fautes de frappe.

#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

Une typedef déclaration introduit un nom qui, dans sa portée, devient un synonyme du type donné par la partie déclaration de type de la déclaration.

Vous pouvez utiliser des déclarations typedef pour construire des noms plus courts ou plus explicites pour les types déjà définis par le langage ou pour les types que vous avez déclarés. Les noms typedef vous permettent d'encapsuler des détails d'implémentation susceptibles de changer.

Contrairement aux classdéclarations , et structunionenum aux déclarations, typedef les déclarations n’introduisent pas de nouveaux types ; elles introduisent de nouveaux noms pour les types existants.

Les noms déclarés à l’aide typedef de l’espace de noms occupent le même espace de noms que d’autres identificateurs (à l’exception des étiquettes d’instruction). Par conséquent, ils ne peuvent pas utiliser le même identificateur qu’un nom précédemment déclaré, sauf dans une déclaration de type classe. Prenons l’exemple suivant :

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

Les règles de masquage de noms qui concernent d’autres identificateurs régissent également la visibilité des noms déclarés à l’aide typedefde . Par conséquent, l'exemple suivant est autorisé 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

Autre instance de masquage de nom :

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

Lorsque vous déclarez un identificateur d’étendue locale par le même nom qu’un typedefou lorsque vous déclarez un membre d’une structure ou d’une union dans la même étendue ou dans une étendue interne, le spécificateur de type doit être spécifié. Par exemple :

typedef char FlagType;
const FlagType x;

Pour réutiliser le nom FlagType pour un identificateur, un membre de structure ou un membre d'union, le type doit être fourni :

const int FlagType;  // Type specifier required

Il ne suffit pas de déclarer

const FlagType;      // Incomplete specification

parce que la FlagType valeur est prise pour faire partie du type, et non un identificateur qui est redéclaré. Cette déclaration est considérée comme une déclaration illégale, similaire à :

int;  // Illegal declaration

Vous pouvez déclarer tout type avec typedef, y compris des types de pointeur, de fonction et de tableau. Vous pouvez déclarer un nom typedef pour un pointeur vers une structure ou un type union avant de définir la structure ou le type union, tant que la définition a la même visibilité que la déclaration.

Exemples

Une utilisation des typedef déclarations consiste à rendre les déclarations plus uniformes et plus compactes. Par exemple :

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

typedef Pour spécifier des types fondamentaux et dérivés dans la même déclaration, vous pouvez séparer les déclarateurs avec des virgules. Par exemple :

typedef char CHAR, *PSTR;

L’exemple suivant fournit le type DRAWF pour une fonction qui ne retourne aucune valeur et qui accepte deux arguments int :

typedef void DRAWF( int, int );

Après l’instruction ci-dessus typedef , la déclaration

DRAWF box;

équivaut à la déclaration

void box( int, int );

typedef est souvent combiné avec struct les types définis par l’utilisateur pour déclarer et nommer :

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

La typedef déclaration peut être utilisée pour redeclarer le même nom pour faire référence au même type. Par exemple :

Fichier source file1.h :

// file1.h
typedef char CHAR;

Fichier source file2.h :

// file2.h
typedef char CHAR;

Fichier source prog.cpp :

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

Le fichier prog.cpp inclut deux fichiers d’en-tête, qui contiennent typedef des déclarations pour le nom CHAR. Tant que les deux déclarations désignent le même type, cette redéclaration est acceptable.

Impossible typedef de redéfinir un nom qui a été précédemment déclaré en tant que type différent. Considérez cette alternative file2.h:

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

Le compilateur émet une erreur en prog.cpp raison de la tentative de redéclarer le nom CHAR pour faire référence à un autre type. Cette stratégie s’étend aux constructions telles que :

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;

typedefs en C++ et C

L’utilisation du typedef spécificateur avec des types de classes est prise en charge en grande partie en raison de la pratique ANSI C de la déclaration de structures non nommées dans les typedef déclarations. Par exemple, de nombreux programmeurs C utilisent l’idiome suivant :

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

L'avantage de ce type de déclaration est qu'il permet des déclarations telles que :

POINT ptOrigin;

plutôt que :

struct point_t ptOrigin;

En C++, la différence entre typedef les noms et les types réels (déclarées avec les classmot clé structunion, et enum les noms) est plus distincte. Bien que la pratique C de déclarer une structure sans nom dans une typedef instruction fonctionne toujours, elle ne fournit aucun avantage notationnel tel qu’il le fait dans C.

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

L’exemple précédent déclare une classe nommée à l’aide de la syntaxe de classe typedef non nomméePOINT. POINT est considérée comme un nom de classe. Toutefois, les restrictions suivantes s'appliquent aux noms introduits de cette façon :

  • Le nom (le synonyme) ne peut pas apparaître après un préfixe , structou union un classpréfixe.

  • Le nom ne peut pas être utilisé comme constructeur ou nom de destructeur dans une déclaration de classe.

En résumé, cette syntaxe ne fournit aucun mécanisme pour l’héritage, la construction ou la destruction.