typedef vs using в C++: полное руководство для современного разработчика
Вы когда-нибудь задумывались, почему в коде на C++ некоторые разработчики пишут typedef, а другие — using? Кажется, что оба делают одно и то же — создают псевдонимы для типов. Но так ли это на самом деле? Если вы хотите писать чистый, современный код и не попадать в ловушки устаревших конструкций, эта статья для вас.
Мы разберёмся, чем отличаются typedef и using в C++, когда что использовать, и почему сообщество C++ всё чаще склоняется в сторону using. Готовы? Поехали!
Краткая история: от C до C++11
typedef пришёл к нам из языка C. Это классический способ объявить псевдоним для типа. Выглядит он так:
typedef unsigned long long ULL;
typedef std::vector<std::string> StringVector;
typedef void (*FunctionPtr)(int, double);
Синтаксис, мягко говоря, не самый интуитивный. Особенно это заметно с указателями на функции — даже опытные разработчики иногда запинаются, читая такие объявления.
В C++11 появилась новая конструкция — using (или alias declaration). Она выглядит гораздо естественнее:
using ULL = unsigned long long;
using StringVector = std::vector<std::string>;
using FunctionPtr = void (*)(int, double);
Сразу видно: слева — имя, справа — тип. Читается как «присваивание», что интуитивно понятно любому программисту.
Главное отличие: работа с шаблонами
Здесь кроется самое важное различие между typedef и using. using поддерживает alias templates (шаблонные псевдонимы), а typedef — нет.
Давайте посмотрим на примере. Допустим, вы хотите создать псевдоним для std::unique_ptr с кастомным удалителем:
// С using — легко и красиво
template <typename T>
using CustomPtr = std::unique_ptr<T, void(*)(T*)>;
// С typedef — невозможно напрямую!
// Придётся городить обёртки через структуры
template <typename T>
struct CustomPtrOld {
typedef std::unique_ptr<T, void(*)(T*)> type;
};
// Использование:
CustomPtr<int> ptr1; // using — просто
CustomPtrOld<int>::type ptr2; // typedef — муторно
Чувствуете разницу? using делает код чище и короче. А если вам нужно использовать такой псевдоним внутри шаблонного класса или функции, разница становится ещё заметнее:
template <typename T>
class Container {
public:
// С using
using Ptr = CustomPtr<T>;
// С typedef — нужно typename и ::type
typedef typename CustomPtrOld<T>::type OldPtr;
private:
Ptr data;
OldPtr oldData;
};
Читаемость кода: очевидный победитель
Даже для простых типов using выигрывает в читаемости. Сравните:
// С typedef — непонятно, где имя, где тип
typedef std::map<std::string, std::vector<int>> StringToIntVectorMap;
// С using — всё очевидно
using StringToIntVectorMap = std::map<std::string, std::vector<int>>;
Когда вы читаете код, ваш глаз естественным образом сканирует конструкцию слева направо. using следует этому принципу: сначала имя, потом тип. typedef же заставляет вас сначала прочитать тип, а потом имя — это противоречит интуиции.
Указатели на функции: боль typedef
Указатели на функции — это, пожалуй, самая нелюбимая часть C++ для многих разработчиков. И typedef делает их ещё более запутанными:
// typedef — читайте как заклинание
typedef void (*Callback)(int, const std::string&);
// using — как обычная переменная
using Callback = void (*)(int, const std::string&);
С using вы просто пишете «Callback — это указатель на функцию, которая принимает int и const string& и ничего не возвращает». С typedef нужно помнить, что имя прячется где-то в середине.
Совместимость и обратная связь
Важный вопрос: можно ли использовать using в старых проектах? Если ваш компилятор поддерживает C++11 (а это практически все современные компиляторы, включая GCC, Clang, MSVC), то да. using — это часть стандарта C++11, и он полностью обратно совместим с typedef.
Более того, using не ломает существующий код. Вы можете спокойно заменять typedef на using в старых проектах — это не вызовет ошибок компиляции.
Производительность: есть ли разница?
Короткий ответ: нет. И typedef, и using — это исключительно синтаксические конструкции. Они не порождают никакого машинного кода и не влияют на производительность. Компилятор просто подставляет один тип вместо другого на этапе компиляции.
Так что выбирайте на основе читаемости и удобства, а не производительности.
Когда всё же использовать typedef?
Честно говоря, в современном C++ (C++11 и новее) практически нет причин использовать typedef. Единственный сценарий — это поддержка очень старых кодовых баз, где компилятор не поддерживает C++11. Но если вы пишете новый код или поддерживаете проект, который уже использует C++11, всегда используйте using.
Исключение: если вы пишете код, который должен компилироваться и как C, и как C++ (например, заголовочные файлы для смешанных проектов). В таких случаях typedef — единственный вариант, так как C не знает о using.
Практический пример: рефакторинг старого кода
Представьте, что вы работаете с легаси-проектом, где весь код написан на C++98. Вы решили обновиться до C++17. Вот как можно провести рефакторинг:
// Было (C++98)
typedef std::vector<std::pair<int, std::string>> PairList;
typedef std::map<std::string, PairList> Dictionary;
class Processor {
public:
typedef void (*Handler)(const Dictionary&);
void setHandler(Handler h);
};
// Стало (C++17)
using PairList = std::vector<std::pair<int, std::string>>;
using Dictionary = std::map<std::string, PairList>;
class Processor {
public:
using Handler = void (*)(const Dictionary&);
void setHandler(Handler h);
};
Код стал чище, понятнее,