typedef vs using в C++: что выбрать и чем они отличаются на самом деле?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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);
};

Код стал чище, понятнее,

Блоги

Рекомендаций по книгам