Псевдонимы и определения типов (C++)

Объявление псевдонима можно использовать для объявления имени, используемого в качестве синонима для ранее объявленного типа. (Этот механизм также называется неформальным псевдонимом типа. Этот механизм также можно использовать для создания шаблона псевдонима, который может быть полезен для пользовательских распределителей.

Синтаксис

using identifier = type;

Замечания

идентификатор
Имя псевдонима.

type
Идентификатор типа, для который вы создаете псевдоним.

Псевдоним не вводит новый тип и не может изменить значение существующего имени типа.

Простейшая форма псевдонима эквивалентна механизму typedef C++03:

// C++11
using counter = long;

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

Обе эти формы позволяют создавать переменные типа counter. Псевдоним типа для 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);

Псевдонимы также работают с указателями функций, но гораздо более удобочитаемыми, чем эквивалентный типдф:

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

Ограничение typedef механизма заключается в том, что он не работает с шаблонами. Напротив, синтаксис псевдонима типа в C ++11 позволяет создавать шаблоны псевдонимов:

template<typename T> using ptr = T*;

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

Пример

В следующем примере показано, как использовать шаблон псевдонима с пользовательским механизмом выделения памяти — в данном случае целочисленным векторным типом. Можно заменить любой тип int , чтобы создать удобный псевдоним, чтобы скрыть сложные списки параметров в основном функциональном коде. Используя пользовательский распределитель во всем коде, вы можете улучшить удобочитаемость и снизить риск возникновения ошибок, вызванных опечатками.

#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 вводит имя, которое в область становится синонимом типа, заданного частью объявления типа.

Объявления typedef можно использовать для создания более коротких или более значимых имен для типов, уже определенных языком или типами, объявленными вами. Имена typedef позволяют инкапсулировать детали реализации, которые могут измениться.

В отличие от объявлений classstructи unionenum объявлений, объявления typedef не вводят новые типы; они вводят новые имена для существующих типов.

Имена, объявленные с использованием typedef того же пространства имен, что и другие идентификаторы (кроме меток инструкций). Поэтому они не могут использовать тот же идентификатор, что и ранее объявленное имя, за исключением объявления типа класса. Рассмотрим следующий пример:

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

Правила скрытия имен, относящиеся к другим идентификаторам, также управляют видимостью имен, объявленных с помощью typedef. Поэтому следующий код допустим в 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

Другой экземпляр имени скрыт:

// typedef_specifier1.cpp
typedef char FlagType;

int main()
{
}

void myproc( int )
{
    int FlagType;
}

При объявлении идентификатора локального область с таким же именем, как и typedefпри объявлении члена структуры или объединения в том же область или во внутреннем область, необходимо указать описатель типа. Например:

typedef char FlagType;
const FlagType x;

Чтобы повторно использовать имя FlagType для идентификатора, члена структуры или члена объединения, необходимо указать тип:

const int FlagType;  // Type specifier required

Недостаточно сказать

const FlagType;      // Incomplete specification

так как принимается FlagType как часть типа, а не идентификатор, который выполняется повторно. Это объявление принимается как незаконное объявление, аналогичное следующему:

int;  // Illegal declaration

Можно объявить любой тип с typedefпомощью указателя, функции и типов массивов. Имя typedef для типа указателя на структуру или объединение можно объявить до определения типа структуры или объединения, если только определение находится в той же области видимости, что и объявление.

Примеры

Одним из способов использования объявлений является создание объявлений typedef более единообразным и компактным. Например:

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 Чтобы указать основные и производные типы в одном объявлении, можно разделить деклараторы с запятыми. Например:

typedef char CHAR, *PSTR;

В следующем примере задан тип DRAWF для функции, не возвращающей никакого значения и принимающей два аргумента int.

typedef void DRAWF( int, int );

После приведенной выше typedef инструкции объявление

DRAWF box;

будет эквивалентно следующему:

void box( int, int );

typedef часто объединяется с struct объявлением и именем определяемых пользователем типов:

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

Повторная отмена типов

Объявление typedef можно использовать для повторного объявления того же имени, чтобы ссылаться на тот же тип. Например:

Исходный файл file1.h:

// file1.h
typedef char CHAR;

Исходный файл file2.h:

// file2.h
typedef char CHAR;

Исходный файл prog.cpp:

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

prog.cpp Файл содержит два файла заголовка, оба из которых содержат typedef объявления для имениCHAR. Если в обеих объявлениях указывается один и тот же тип, такое повторное объявление допустимо.

Не typedef удается переопределить имя, которое ранее было объявлено в качестве другого типа. Рассмотрим эту альтернативу file2.h:

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

Компилятор выдает ошибку из-за prog.cpp попытки повторного объявления имени CHAR для ссылки на другой тип. Эта политика распространяется на такие конструкции, как:

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 в C++ и C

typedef Использование описателя с типами классов поддерживается в основном из-за практики ANSI C объявления неименованных структур в typedef объявлениях. Например, многие программисты C используют следующую идиому:

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

Преимущество такого объявления заключает в том, что можно выполнять объявления

POINT ptOrigin;

вместо

struct point_t ptOrigin;

В C++разница между typedef именами и реальными типами (объявленными с classпомощью , structunionи enum ключевое слово) более различается. Хотя практика объявления безымяной структуры в typedef инструкции по-прежнему работает, она не предоставляет нотационных преимуществ, как это делает в C.

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

В предыдущем примере объявляется класс с именем с использованием синтаксиса неназванного POINT класса typedef . POINT считается именем класса, однако к именам, предоставленным таким образом, применяются следующие ограничения.

  • Имя (синоним) не может отображаться после classstructпрефикса или union префикса.

  • Имя нельзя использовать в качестве конструктора или деструктора в объявлении класса.

В итоге этот синтаксис не предоставляет никакого механизма для наследования, построения или уничтожения.