Udostępnij za pośrednictwem


Aliasy i definicje typów (C++)

Możesz użyć deklaracji aliasu, aby zadeklarować nazwę do użycia jako synonim dla wcześniej zadeklarowanego typu. (Ten mechanizm jest również nazywany nieformalnie aliasem typu). Za pomocą tego mechanizmu można również utworzyć szablon aliasu, który może być przydatny w przypadku niestandardowych alokatorów.

Składnia

using identifier = type;

Uwagi

Identyfikator
Nazwa aliasu.

type
Identyfikator typu, dla którego tworzysz alias.

Alias nie wprowadza nowego typu i nie może zmienić znaczenia istniejącej nazwy typu.

Najprostsza forma aliasu jest odpowiednikiem typedef mechanizmu z języka C++03:

// C++11
using counter = long;

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

Obie te formularze umożliwiają tworzenie zmiennych typu counter. Coś bardziej przydatnego może być alias typu, taki jak ten dla 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);

Aliasy działają również ze wskaźnikami funkcji, ale są znacznie bardziej czytelne niż równoważna definicja typu:

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

Ograniczenie typedef mechanizmu polega na tym, że nie działa z szablonami. Jednak składnia aliasu typu w języku C++11 umożliwia tworzenie szablonów aliasów:

template<typename T> using ptr = T*;

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

Przykład

W poniższym przykładzie pokazano, jak używać szablonu aliasu z niestandardowym alokatorem — w tym przypadku typem wektora całkowitego. Możesz zastąpić dowolny typ , int aby utworzyć wygodny alias, aby ukryć złożone listy parametrów w głównym kodzie funkcjonalnym. Korzystając z niestandardowego alokatora w całym kodzie, możesz zwiększyć czytelność i zmniejszyć ryzyko wprowadzenia usterek spowodowanych przez literówki.

#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

Deklaracja typedef wprowadza nazwę, która w zakresie staje się synonimem typu podanego przez część deklaracji typu deklaracji.

Za pomocą deklaracji typedef można tworzyć krótsze lub bardziej znaczące nazwy typów zdefiniowanych już przez język lub dla zadeklarowanych typów. Nazwy zdefiniowane przez typedef pozwalają na hermetyzację szczegółów implementacji, które mogą się zmieniać.

W przeciwieństwie do classdeklaracji , struct, unioni enum deklaracje typedef nie wprowadzają nowych typów; wprowadzają nowe nazwy istniejących typów.

Nazwy zadeklarowane przy użyciu typedef zajmują tę samą przestrzeń nazw co inne identyfikatory (z wyjątkiem etykiet instrukcji). W związku z tym nie mogą używać tego samego identyfikatora co wcześniej zadeklarowana nazwa, z wyjątkiem deklaracji typu klasy. Rozważmy następujący przykład:

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

Reguły ukrywania nazw odnoszące się do innych identyfikatorów określają również widoczność nazw zadeklarowanych przy użyciu polecenia typedef. W związku z tym poniższy przykład jest legalny w języku 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

Inne wystąpienie ukrywania nazwy:

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

W przypadku deklarowania identyfikatora zakresu lokalnego o takiej samej nazwie jak typedef, lub w przypadku deklarowania elementu członkowskiego struktury lub unii w tym samym zakresie lub w zakresie wewnętrznym należy określić specyfikator typu. Przykład:

typedef char FlagType;
const FlagType x;

Aby ponownie użyć nazwy FlagType dla identyfikatora, elementu struktury lub unii musi zostać dostarczony typ:

const int FlagType;  // Type specifier required

Nie wystarczy powiedzieć

const FlagType;      // Incomplete specification

ponieważ element FlagType jest traktowany jako część typu, a nie identyfikator, który jest ponownie zadeklarowany. Deklaracja ta jest uważana za nielegalną deklarację podobną do:

int;  // Illegal declaration

Można zadeklarować dowolny typ za pomocą typedefpolecenia , w tym wskaźnika, funkcji i typów tablic. Można zadeklarować nazwę typedef wskaźnika na strukturę lub unię przed zdefiniowaniem struktury lub unii, tak długo jak definicja ma taką samą widoczność jak deklaracja.

Przykłady

Jednym z zastosowań deklaracji typedef jest uczynienie deklaracji bardziej jednolitymi i kompaktowymi. Przykład:

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

Aby użyć typedef do określenia podstawowych i pochodnych typów w tej samej deklaracji, można oddzielić deklaratory przecinkami. Przykład:

typedef char CHAR, *PSTR;

W następującym przykładzie podano typ DRAWF dla funkcji niezwracającej wartości i przyjmującej dwa argumenty typu int:

typedef void DRAWF( int, int );

Po powyższej typedef instrukcji deklaracja

DRAWF box;

byłaby równoważna z deklaracją

void box( int, int );

typedef jest często łączone z elementem struct w celu deklarowania i nazywania typów zdefiniowanych przez użytkownika:

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

Ponowne deklarowanie definicji typów

Deklarację typedef można użyć do ponownego zgłoszenia tej samej nazwy, aby odwoływać się do tego samego typu. Przykład:

Plik file1.hźródłowy:

// file1.h
typedef char CHAR;

Plik file2.hźródłowy:

// file2.h
typedef char CHAR;

Plik prog.cppźródłowy:

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

prog.cpp Plik zawiera dwa pliki nagłówkowe, z których oba zawierają typedef deklaracje dla nazwy CHAR. Tak długo, jak obie deklaracje odnoszą się do tego samego typu, taka ponowna deklaracja jest akceptowalna.

Nie typedef można ponownie zdefiniować nazwy, która została wcześniej zadeklarowana jako inny typ. Rozważmy tę alternatywę file2.h:

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

Kompilator zgłasza błąd z prog.cpp powodu próby ponownego zadeklarowania nazwy CHAR w celu odwoływania się do innego typu. Te zasady rozszerzają się na konstrukcje, takie jak:

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;

definicje typów w języku C++ a C

Użycie specyfikatora typedef z typami klas jest obsługiwane w dużej mierze z powodu praktyki ANSI C deklarowania nienazwanych struktur w typedef deklaracjach. Na przykład wielu programistów języka C używa następującego idiomu:

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

Zaletą takiej deklaracji jest to, że umożliwia deklaracje takie jak:

POINT ptOrigin;

Zamiast:

struct point_t ptOrigin;

W języku C++różnica między nazwami i typami typedef rzeczywistymi (zadeklarowanymi za pomocą classsłów kluczowych , struct, unioni enum ) jest bardziej odrębna. Mimo że praktyka języka C deklarowania struktury bez nazw w typedef instrukcji nadal działa, nie zapewnia żadnych korzyści notacyjnych, jak to ma miejsce w języku C.

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

W poprzednim przykładzie zadeklarowano klasę o nazwie POINT przy użyciu nienazwanej składni klasy typedef . POINT jest traktowana jako nazwa klasy; jednak następujące ograniczenia dotyczą nazw wprowadzonych w ten sposób:

  • Nazwa (synonim) nie może być wyświetlana po prefiksie class, structlub union .

  • Nazwa nie może być używana jako konstruktor lub nazwa destruktora w deklaracji klasy.

Podsumowując, ta składnia nie zapewnia żadnego mechanizmu dziedziczenia, budowy ani zniszczenia.