Когда использовать typedef?

14

Я немного запутался, если и когда я должен использовать typedef в C ++. Я чувствую, что это баланс между читабельностью и ясностью.

Вот пример кода без каких-либо определений типов:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    static std::map<std::tuple<std::vector<int>::const_iterator,
                               std::vector<int>::const_iterator>,
                    int> lookup_table;

    std::map<std::tuple<std::vector<int>::const_iterator,
                        std::vector<int>::const_iterator>, int>::iterator lookup_it =
        lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Довольно уродливый ИМО. Поэтому я добавлю несколько typedef в функцию, чтобы она выглядела лучше:

int sum(std::vector<int>::const_iterator first, 
        std::vector<int>::const_iterator last)
{
    typedef std::tuple<std::vector<int>::const_iterator,
                       std::vector<int>::const_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Код все еще немного неуклюж, но я избавляюсь от большинства кошмарных материалов. Но есть еще итераторы типа int, этот вариант избавляет от них:

typedef std::vector<int>::const_iterator Input_iterator;

int sum(Input_iterator first, Input_iterator last)
{
    typedef std::tuple<Input_iterator, Input_iterator> Lookup_key;
    typedef std::map<Lookup_key, int> Lookup_table;

    static Lookup_table lookup_table;

    Lookup_table::iterator lookup_it = lookup_table.find(lookup_key);

    if (lookup_it != lookup_table.end())
        return lookup_it->second;            

    ...
}

Это выглядит чисто, но все еще читаемо?

Когда я должен использовать typedef? Как только у меня кошмарный тип? Как только это происходит не раз? Где я должен их положить? Должен ли я использовать их в сигнатурах функций или оставить их для реализации?

futlib
источник
1
Не дублированный, но несколько связанный с моим вопросом programmers.stackexchange.com/questions/130679/…
c0da
typedef Input_iterator std::vector<int>::const_iterator;назад
Пер Йоханссон
1
Есть разница между читабельностью и ясностью?
Нил
Когда #defineне достаточно хорошо.
Томас Эдинг

Ответы:

6

Ваш последний пример очень удобен для чтения, но это зависит от того, где вы определяете typedef. Локальные определения типов (как в вашем втором примере) IMVHO почти всегда выигрывают.

Мне все еще нравится ваш третий пример, но вы можете подумать об именах и дать имена итераторам, которые сообщают о намерениях контейнера.

Другой вариант - сделать шаблон из вашей функции, чтобы он работал и с другими контейнерами. По линии

template <typename Input_iterator> ... sum(Input_iterator first, Input_iterator last) 

что также очень в духе STL.

Фабио Фракасси
источник
2

Думайте о typedefкак об объявлении переменной, эквивалентном функции: она есть, чтобы вы ...

  • ... не нужно повторяться при повторном использовании одного и того же типа (что делают ваши первые два примера).
  • ... может скрывать кровавые детали типа, поэтому они не всегда демонстрируются.
  • ... убедитесь, что изменения в типе отражены везде, где они используются.

Лично я остаюсь, если мне приходится читать длинные имена типа, как std::vector<int>::const_iteratorнеоднократно.

Ваш третий пример не повторяется излишне и его легче всего читать.

Blrfl
источник
1

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

  • Если вам нужно typedef, скорее всего, звонящие тоже будут, особенно как в вашем примере, где это используется в аргументах.
  • Если вам по какой-либо причине необходимо изменить тип, в том числе заменить его собственным классом, вам нужно будет сделать это только в одном месте.
  • Это делает вас менее склонным к ошибкам из-за многократного написания сложных типов.
  • Он скрывает ненужные детали реализации.

Кроме того, ваш код напоминания будет намного чище, если вы абстрагируете его дальше, например:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);
Карл Билефельдт
источник
Ваше предложение может выглядеть чище, но оно тратит время, выполняя поиск дважды.
Дерек Ледбеттер
Да, это был преднамеренный компромисс. Есть способы сделать это с одним поиском, которые почти так же чисты, особенно если вы не беспокоитесь о безопасности потоков.
Карл Билефельдт