Коли використовувати 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;            

    ...
}

Досить потворний ІМО. Тож я додаю кілька типів у функції, щоб вона виглядала приємніше:

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? Як тільки у мене є тип кошмару? Як тільки це трапляється не раз? Куди мені їх покласти? Чи слід використовувати їх у підписах функцій або тримати їх до реалізації?


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кілька разів.

Третій приклад не повторюється без зайвих зусиль і його найлегше читати.


1

typedefдекларації служать по суті тій самій цілі, що і інкапсуляція. З цієї причини вони майже завжди найкраще вписуються у файл заголовка, дотримуючись тих самих умов іменування, що і ваші класи, оскільки:

  • Якщо вам потрібно typedef, шанси на те, що викликає абонентів, теж буде, особливо як у вашому прикладі, де він використовується в аргументах.
  • Якщо вам потрібно змінити тип з будь-якої причини, в тому числі замінити його власним класом, вам доведеться зробити це лише в одному місці.
  • Це робить вас менш схильними до помилок через багаторазове написання складних типів.
  • Він приховує непотрібні деталі реалізації.

Крім того, ваш код запам'ятовування був би набагато чистішим, якщо ви його абстрагували далі, наприклад:

if (lookup_table.exists(first, last))
    return lookup_table.get(first, last);

Ваша пропозиція може виглядати більш чистою, але вона витрачає час, роблячи пошук двічі.
— Дерек Ледбеттер

Так, це була навмисна компроміс. Існують способи зробити це за допомогою одного пошуку, який майже такий же чистий, особливо якщо ви не переживаєте за безпеку ниток.
— Карл Білефельдт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.