Правильное использование typedef в С++

У меня есть сотрудники, которые иногда используют typedef, чтобы не печатать. Например:

typedef std::list<Foobar> FoobarList;
...
FoobarList GetFoobars();

Лично я всегда ненавижу использовать такой код, в основном потому, что он заставляет меня искать typedef, поэтому я могу сказать, как его использовать. Я также чувствую, что такая вещь - потенциальный скользкий склон... Если вы это сделаете, почему бы вам не сделать это больше? (довольно скоро ваш код полностью запутался). Я нашел этот ВОПРОС по этому вопросу:

когда я должен использовать typedef в C

У меня есть два вопроса: 1) Неужели я по-настоящему один в этом нелюбим? 2) Если подавляющее большинство людей думает, что это типичное использование typedef в порядке, какие критерии вы используете для определения того, нужно ли вводить тип?

Ответы

Ответ 1

Два больших аргумента для этого типа typedef - это сокращенная типизация, о которой вы уже упоминали, и легкость перехода на новый тип контейнера. FoobarList может поддерживаться с помощью vector или list или deque, и для переключения часто требуется просто изменить typedef.

Ваша неприязнь к ним, когда дело доходит до их поиска, значительно сокращается при работе с IDE, так как я могу просто навести курсор на имя типа, а IDE сообщает мне, что он определил как.

Более полезными являются ситуации, когда у вас есть вложенные контейнеры - вы можете дать именам некоторые семантические значения без необходимости определять целые классы:

typedef std::list<Foobar> FoobarList;
typedef std::map <string, FoobarList> GizmosToFoobarsMap;

Вы также можете сохранить LOT ввода при работе с итераторами этих типов (хотя это уменьшено теперь, когда С++ 0x имеет auto.)

Ответ 2

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

template <class Iterator>
struct iterator_traits {
  typedef typename Iterator::iterator_category iterator_category;
  typedef typename Iterator::value_type        value_type;
  typedef typename Iterator::difference_type   difference_type;
  typedef typename Iterator::pointer           pointer;
  typedef typename Iterator::reference         reference;
};

template <class T>
struct iterator_traits<T*> {
  typedef random_access_iterator_tag iterator_category;
  typedef T                          value_type;
  typedef ptrdiff_t                  difference_type;
  typedef T*                         pointer;
  typedef T&                         reference;
};

Что касается "ярлыка" использования typedef - я думаю, что это прекрасно, когда локализуется в файле реализации. У меня есть то же правило здесь, что и для using namespace - если он меня печатает и не путает других, идите за ним.

Ответ 3

Мне нравятся typedefs, потому что они улучшают читаемость. Возьмите std::vector<std::string>::const_iterator по всему месту или даже std::map<std::string, std::vector<std::string> >::iterator. Половина вашего кода заканчивается просто описанием типов, и куча typedefs значительно упрощает это. Я не думаю, что слишком много препятствий для понимания: IDE могут обычно сообщать вам тип, если вы наведите указатель мыши на него или переходите к определению, а работа с кодом обычно требует, чтобы вы все равно понимали такие вещи.

Ответ 4

Мне нравится typedef, потому что он позволяет изменять тип контейнера позже (скажем, профилирование показало, что std::deque будет быстрее, чем std::list). без изменения всего кода.

И если вы задаетесь вопросом: я не возражаю против того, что вы называете список, хотя это может быть std::deque. В этом контексте я вижу "список" как концептуальный термин, а не структуру данных. (Вы не делаете свои списки покупок как дважды связанные списки, не так ли?)

Ответ 5

Лично я также typedef материал STL, список и итератор (Foos и FooIterator). Используемый везде делает очень понятным (imo) стиль, к которому вы привыкли к 2-му происшествию.

Кроме того, вы можете просто навести указатель мыши на тип, чтобы увидеть, что это действительно означает в любой реальной среде IDE (vs или eclipse), поэтому у вас есть вся информация, которая вам нужна прямо в ее месте использования, а не для прокрутки 3 экранов справа, чтобы прочитать полную строку.

С ключевым словом С++ 0x auto это больше не понадобится. Да, я верю в ключевое слово С# var, и снова вы можете навести курсор мыши на него, чтобы увидеть тип, если у вас есть сомнения!

Ответ 6

Я тоже не делаю этого в своем коде, поэтому, думаю, вы не одиноки. На мой взгляд, это еще одна вещь, которую читатель должен искать. У плохого чтения кода, как правило, более чем достаточно.

Ответ 7

В вашем примере FoobarList ничем не отличается от любого другого класса; вам все равно придется искать определение или документацию, чтобы знать, как его использовать; и вы не использовали бы это как аргумент для не использования классов!

В этом случае, как только вы определили, что это STd std:: list, вы знаете, как его использовать или, по крайней мере, где найти документацию хорошего качества; что, вероятно, больше, чем вы можете сказать для большинства классов, которые могут быть определены в вашем проекте.

Это говорит о том, что я амбивалентен относительно того, хорошая ли это идея или нет; Я просто думаю, что это в основном безвредно.

Ответ 8

Я лично считаю, что typedef очень полезны при работе с шаблоном кода, как при написании шаблонов, так и при их создании.

Написание шаблонов:

typedefs необходимы для метапрограммирования шаблонов. Например, удаление const:

template<typename T>
struct RemoveConst
{
typedef T Type;
};

template<>
struct RemoveConst<const T>
{
typedef T Type;
};

Теперь мы можем получить доступ к типу const-less из любого T путем создания экземпляра RemoveConst:: Type

Создание шаблонов:

Как и многое в программировании, правильный инструмент для правильной работы. typedefs, например ваш пример

typedef std::list<Foobar> FoobarList;
...
FoobarList GetFoobars();

Звучит вполне разумно, главным образом потому, что типизированное имя является описательным (FoobarList - это список Foobars). Это очень полезно, особенно при работе с контейнерами STL или любыми другими типами шаблонов, которые используют один и тот же интерфейс. представьте себе следующее объявление класса:

class SomeClass
{
...
std::vector<int> mContainer;
};

Этот класс, вероятно, будет перебирать элементы Container, что приведет к тому, что код будет похож на:

for(std::vector<int>::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}

Теперь представьте себе, что после написания вышеперечисленного for-loop в 5 различных методах, которые вы постоянно вставляете в середину массива, и что std:: list будет намного лучше подходит для задания. В этом случае вам нужно будет пройти через каждый экземпляр std::vector:: iterator и изменить объявление.

Вместо этого, что вы можете сделать, это typedef контейнер, который вы используете внутри класса:

class SomeClass
{
typedef std::vector<int> IntContainer;
...
IntContainer mContainer;
};

Это может позволить вам написать очень общий код, который может быть очень легко модифицируемым.

for(IntContainer::iterator It = mContainer.begin(); It != mContainer.end(); ++It)
{
}

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

Ответ 9

Вы видите typedefs много, когда вам нужно написать код на С++, который работает на разных платформах, потому что размер базовых типов данных не фиксирован. Например, виртуальная машина Java имеет typedefs для основных типов для разных компиляторов.

Ответ 10

Я думаю, что typedef - хорошая практика в этом случае. В то время как синтаксис шаблона ставит перед вами параметры, они обычно являются вещами, которые можно было бы инкапсулировать в объектно-ориентированную среду.

typedef'ing шаблон выполняет эту инкапсуляцию.

Ответ 11

Если я хочу, чтобы тип окончания был строго типизирован, я обычно предпочитаю наследование по typedef, например. class CMyMap : public CMap<int, int> {...}

Таким образом, CMyMap строго типизирован - я просто не могу передать любой CMap<int, int> в метод, который принимает CMyMap в качестве параметра. typedef просто сообщает компилятору "другое имя для", но определение класса определяет новый тип.

Если я определил функцию void foo (const CMyMap & map);

но я использовал typedef для определения CMyMap, например. typdef CMap<int, int> CMyMap; то кто-то мог сделать CMap<int, int> myMap; Foo (MyMap); и компилятор будет вполне счастлив.

Но если я действительно хочу, чтобы foo был строго типизирован и ТОЛЬКО принимал CMyMap, тогда наследование - это мой механизм выбора для "ввода".

BTW, CMyMap является родовым, но более конкретным может быть CPhoneNumber2Index, где номер телефона представлен как целое число, а индекс - целое число. Здесь очевидно, что мой CPhoneNumber2Index - это не просто карта из целого числа в другое целое число. Так что foo может быть строка ReverseLookup (const CPhoneNumber2Index & PhoneBook, int iPhoneNumber); который показывает, что ReverseLookup имеет какое-то отношение к номерам телефонов, а не CMap<int, int>

Ответ 12

После некоторой тяжелой работы с шаблонами typedef ведет себя как нормальное присвоение переменной...