6.13 – typedef и псевдонимы типов

Добавлено 15 мая 2021 в 22:44

typedef позволяет программисту создать псевдоним для типа данных и использовать этот псевдоним вместо фактического имени типа. typedef буквально означает «type definition» (определение типа).

Чтобы объявить «переопределение типа», просто используйте ключевое слово typedef, за которым следует тип для псевдонима, за которым следует имя псевдонима:

typedef double distance_t; // определяем distance_t как псевдоним для типа double
 
// Следующие две инструкции эквивалентны:
double howFar;
distance_t howFar;

По соглашению имена typedef объявляются с использованием суффикса "_t". Это помогает указать, что идентификатор представляет собой тип, а не переменную или функцию, а также помогает предотвратить конфликты имен с другими идентификаторами.

Обратите внимание, что typedef не определяет новый тип. Скорее, он просто создает псевдоним (другое имя) для существующего типа. typedef можно использовать как замену везде, где можно использовать обычный тип.

Несмотря на то, что следующее семантически не имеет смысла, C++ допускает подобный код:

int main()
{
    typedef long miles_t;
    typedef long speed_t;
 
    miles_t distance { 5 };
    speed_t mhz  { 3200 };
 
    // Следующее корректно, потому что и distance, и mhz на самом деле имеют тип long
    distance = mhz;
 
    return 0;
}

typedef и псевдонимы типов подчиняются тем же правилам области видимости, что и переменные. Переопределения miles_t и speed_t можно использовать только в функции main(). Если бы они были помещены в другую функцию, main() не смогла бы получить к ним доступ. Если бы они были размещены вне main(), в глобальной области видимости, все функции могли бы получить к ним доступ.

Однако у typedef есть несколько проблем. Во-первых, легко забыть, что на первом месте: имя типа или определение типа. Что правильно?

typedef distance_t double; // некорректно
typedef double distance_t; // корректно

Я не могу вспомнить.

Во-вторых, синтаксис typedef становится уродливым при использовании более сложных типов, особенно указателей на функции (которые мы рассмотрим в уроке «10.9 – Указатели на функции»):

Псевдонимы типов

Чтобы помочь решить эти проблемы, для typedef был добавлен улучшенный синтаксис, имитирующий способ объявления переменных. Этот синтаксис называется псевдонимом типа (type alias).

Например, следующий typedef:

typedef double distance_t; // определяем distance_t как псевдоним для типа double

можно объявить как следующий псевдоним типа:

using distance_t = double; // определяем distance_t как псевдоним для типа double

Эти две инструкции функционально эквивалентны.

Обратите внимание, что хотя синтаксис псевдонима типа использует ключевое слово using, это перегруженное значение и не имеет ничего общего с инструкциями using, относящимися к пространствам имен. Таким образом, на него не распространяется правило «не использовать using namespace».

Этот синтаксис псевдонима типа более понятен для более сложных случаев определения типов и должен быть предпочтительным.

Использование псевдонимов типов для повышения читабельности

Одно из применений псевдонимов типов – помочь с документацией и читабельностью. Имена типов данных, такие как char, int, long, double и bool, хороши для описания того, какой тип возвращает функция, но чаще мы хотим знать, какой цели служит возвращаемое значение.

Например, рассмотрим следующую функцию:

int GradeTest();

Мы видим, что возвращаемое значение является целым числом, но что означает это целое число? Буквенная оценка? Количество пропущенных вопросов? Идентификационный номер студента? Код ошибки? Кто знает! int нам ничего не сообщает.

using testScore_t = int;
testScore_t GradeTest();

Однако использование типа возвращаемого значения testScore_t делает очевидным, что функция возвращает тип, представляющий результат теста.

Использование псевдонимов типов для упрощения поддержки кода

Псевдонимы типов также позволяют изменять базовый тип объекта без изменения большого количества кода. Например, если вы использовали short для хранения идентификационного номера студента, но позже решили, что вам нужен long, вам придется прочесать много кода и заменить short на long. Вероятно, будет сложно понять, какой short используется для хранения номеров ID, а какой – для других целей.

Однако с псевдонимом типа всё, что вам нужно сделать, это изменить studentID_t = short; на studentID_t = long;. Однако при изменении типа псевдонима типа на тип из другого семейства типов (например, целочисленный тип на значение с плавающей запятой, или наоборот) следует соблюдать осторожность! Новый тип может иметь проблемы со сравнением или делением целых чисел на числа с плавающей запятой или другие проблемы, которых не было у старого типа.

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

Еще одно преимущество псевдонимов типов заключается в том, что их можно использовать для скрытия деталей, специфичных для платформы. На некоторых платформах int составляет 2 байта, а на других – 4 байта. Таким образом, при написании кода, независимого от платформы, использование int для хранения более 2 байтов информации может быть потенциально опасным.

Поскольку char, short, int и long не указывают их размер, кроссплатформенные программы довольно часто используют псевдонимы типов для определения псевдонимов, которые включают размер типа в битах. Например, int8_t будет 8-разрядным целым числом со знаком, int16_t – 16-разрядным целым числом со знаком, а int32_t – 32-разрядным целым числом со знаком. Подобное использование псевдонимов типов помогает предотвратить ошибки и дает более четкое представление о том, какие предположения были сделаны относительно размера переменной.

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

#ifdef INT_2_BYTES
using int8_t = char;
using int16_t = int;
using int32_t = long;
#else
using int8_t = char;
using int16_t = short;
using int32_t = int;
#endif

На машинах, где int составляет всего 2 байта, с помощью #define может быть определен INT_2_BYTES, и программа будет скомпилирована с верхним набором псевдонимов типов. На машинах, где int равен 4 байтам, оставление INT_2_BYTES неопределенным приведет к использованию нижнего набора псевдонимов типов. Таким образом, int8_t будет преобразовываться в 1-байтовое целое число, int16_t будет преобразовано в 2-байтовое целое число, а int32_t будет преобразовано в 4-байтовое целое число, используя комбинацию char, short, int и long, которая подходит для машины, для которой компилируется программа.

Именно так определяются целочисленные типы фиксированной ширины, такие как std::int8_t (описанные в уроке «4.6 – Целочисленные типы фиксированной ширины и size_t»)!

Здесь также возникает проблема с обработкой int8_t как charstd::int8_t является псевдонимом типа char, а не уникальным типом. Как результат:

#include <cstdint> // для целочисленных типов фиксированной ширины
#include <iostream>
 
int main()
{
    std::int8_t i{ 97 }; // int8_t на самом деле является псевдонимом типа для signed char
    std::cout << i;
 
    return 0;
}

Эта программа печатает:

a

а не 97, потому что std::cout печатает char как символ ASCII, а не как число.

Использование псевдонимов типов для упрощения сложных типов

Хотя до сих пор мы имели дело только с простыми типами данных, в продвинутом C++ вы могли видеть переменную и функцию, объявленные следующим образом:

std::vector<std::pair<std::string, int> > pairlist;
 
bool hasDuplicates(std::vector<std::pair<std::string, int> > pairlist)
{
    // здесь какой-то код
}

Ввод std::vector<std::pair<std::string, int> > везде, где вам нужно использовать этот тип, может оказаться громоздким. Гораздо проще использовать псевдоним типа:

// делаем pairlist_t псевдонимом для этого сумасшедшего типа
using pairlist_t = std::vector<std::pair<std::string, int> >; 
 
pairlist_t pairlist; // создаем экземпляр переменной pairlist_t
 
bool hasDuplicates(pairlist_t pairlist) // используйте pairlist_t в параметре функции
{
    // здесь какой-то код
}

Намного лучше! Теперь нам нужно вводить только pairlist_t вместо std::vector<std::pair<std::string, int> >.

Не волнуйтесь, если вы еще не знаете, что такое std::vector, std::pair или все эти сумасшедшие угловые скобки. Единственное, что вам действительно нужно понять, это то, что псевдонимы типов позволяют вам брать сложные типы и давать им простые имена, что упрощает работу с этими типами и их понимание.

Лучшая практика


Отдавайте предпочтение псевдонимам типов вместо typedef и широко используйте их для документирования назначения ваших типов.

Небольшой тест

Вопрос 1

Возьмем следующий прототип функции:

int printData();

Преобразуйте возвращаемое значение int в псевдоним типа с именем error_t. В ответ включите обе инструкции: инструкцию псевдонима типа, и обновленный прототип функции.

using error_t = int;
error_t printData();

Теги

C++ / CppLearnCpptypedefДля начинающихОбучениеПрограммированиеПсевдоним типа / Type alias

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.