Алиасы
Представьте тип std::unordered_map<std::string, std::vector<std::shared_ptr<Event>>>. Он может появляться в коде десятки раз — в сигнатурах функций, полях классов, возвращаемых типах. Без алиаса каждое такое упоминание — источник опечаток и шум, который мешает читать суть.
Алиас (псевдоним) — альтернативное имя для существующего типа. Он не создаёт новый тип: компилятор видит оба имени как одно и то же. Алиасы решают три задачи:
- Читаемость —
Durationпонятнееstd::chrono::millisecondsв контексте таймера - Доменное моделирование —
using UserId = intсигнализирует о назначении, даже если тип тот же - Управление зависимостями — меняете один алиас, не трогая остальной код
typedef
Старый синтаксис, унаследованный из C:
typedef unsigned long long uint64;
typedef std::vector<std::string> StringList;
typedef void (*Callback)(int, int); // псевдоним для указателя на функцию
Использование:
uint64 counter = 0;
StringList names = {"Alice", "Bob"};
Callback handler = myFunction;
Синтаксис typedef читается не слева направо: имя оказывается «в середине» объявления. Особенно неудобно это проявляется с указателями на функции — тип и имя перемежаются с сигнатурой. Новый код писать на typedef не стоит, но в легаси-коде он встречается повсеместно, поэтому читать его нужно уметь.
using (C++11)
Современный синтаксис — рекомендуется во всём новом коде:
using uint64 = unsigned long long;
using StringList = std::vector<std::string>;
using Callback = void (*)(int, int); // читабельнее, чем typedef
Чтение слева направо: «uint64 — это unsigned long long». Форма имя = тип однозначна, в том числе для указателей на функции.
Шаблонные алиасы
Главное преимущество using перед typedef — поддержка шаблонных алиасов (alias templates). Это не просто синтаксический сахар: шаблонный алиас — полноценный шаблон, который параметризует псевдоним:
template<typename T>
using Vec = std::vector<T>;
template<typename K, typename V>
using HashMap = std::unordered_map<K, V>;
// Использование — как обычные типы
Vec<int> numbers = {1, 2, 3};
HashMap<std::string, int> scores;
С typedef шаблонный алиас невозможен напрямую. Старый обходной путь — структура-обёртка с вложенным type:
// Так делали до C++11
template<typename T>
struct VecHelper {
typedef std::vector<T> type;
};
VecHelper<int>::type numbers; // работает, но некрасиво
Именно от этого паттерна происходит нотация ::type в старых type traits — std::remove_reference<T>::type, std::add_const<T>::type. C++14 добавил удобные алиасы _t:
// До C++14 — через структуру
typename std::remove_reference<T>::type val;
// C++14 — шаблонный алиас в <type_traits>
// template<typename T>
// using remove_reference_t = typename std::remove_reference<T>::type;
std::remove_reference_t<T> val; // чище
// Другие примеры из стандартной библиотеки
std::add_const_t<T> // T → const T
std::decay_t<T> // убирает ссылки и cv-квалификаторы
std::enable_if_t<cond, T> // SFINAE-помощник
std::invoke_result_t<F, Args> // тип результата вызова F(Args...)
Шаблонные алиасы активно применяются везде, где работают шаблоны: при написании обобщённого кода, policy-based design и метапрограммировании.
Алиасы в классах
using внутри класса объявляет вложенный тип — тип-член. Это стандартный способ публиковать информацию о типах в интерфейсе класса:
class Parser {
public:
using Token = std::pair<std::string, int>;
using TokenVec = std::vector<Token>;
TokenVec tokenize(const std::string& input);
// ...
private:
TokenVec tokens_;
};
// Снаружи класса — обращение через ::
Parser::TokenVec result = parser.tokenize(src);
STL-контейнеры публикуют именно такие алиасы: value_type, iterator, reference и т.д. Обобщённый код опирается на них через typename Container::value_type.
Практические примеры
// Сокращение длинных типов STL
using Clock = std::chrono::steady_clock;
using Duration = std::chrono::milliseconds;
using TimePoint = Clock::time_point;
auto start = Clock::now();
// ... работа
Duration elapsed = std::chrono::duration_cast<Duration>(Clock::now() - start);
// Callback-типы — читается как объявление переменной
using Predicate = std::function<bool(int)>;
using Handler = std::function<void(const std::string&)>;
Predicate isEven = [](int n) { return n % 2 == 0; };
// Доменное моделирование
using UserId = int;
using OrderId = int;
void processOrder(UserId user, OrderId order);
// Компилятор не отличает UserId от int, но намерение очевидно из кода
Алиасы и пространства имён
using-объявление вытягивает конкретное имя из пространства имён:
using std::cout;
using std::endl;
cout << "Привет" << endl; // без std::
using namespace в заголовочном файле — анти-паттерн. Он загрязняет пространство имён во всех единицах трансляции, которые включают этот заголовок — включая чужой код, которому вы даже не знаете. Это может вызвать конфликты имён, которые проявятся только в коде пользователя библиотеки.
// mylib.h — ПЛОХО
using namespace std; // навязывает std:: всем, кто включит этот файл
// mylib.h — хорошо
// Используйте std:: явно или using-объявления в .cpp
В .cpp-файлах using namespace в начале функции или файла допустим — он не утекает наружу.
Значение для собеседований
Алиасы редко идут как самостоятельная тема, но регулярно всплывают в контексте шаблонов, type traits и проектирования интерфейсов. Интервьюер проверяет:
- Знаете ли разницу между
typedefиusing— и понимаете ли, что она не только синтаксическая - Умеете ли объяснить, почему шаблонный алиас (
template<typename T> using ...) невозможен сtypedef - Понимаете ли происхождение
::typeв старых type traits и зачем появились суффиксы_tв C++14 - Знаете ли про
usingв классе как способ публикации типов-членов - Понимаете ли разницу между
using Foo = ...(алиас типа) иusing namespace Foo(импорт пространства имён) — это разные механизмы с разной областью видимости
Частая ошибка: «typedef и using — одно и то же, просто разный синтаксис». Это неверно: шаблонные алиасы — принципиальное отличие, которое недоступно через typedef без обёртки-структуры.