Правильное использование typedef в C ++ - PullRequest
13 голосов
/ 28 апреля 2010

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

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

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

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

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

Ответы [ 12 ]

18 голосов
/ 28 апреля 2010

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

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

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

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

Вы также можете сэкономить МНОГО набора текста при работе с итераторами этих типов (хотя теперь C ++ 0x уменьшен на auto.)

9 голосов
/ 28 апреля 2010

typedef s необходимы для программирования 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 s - я думаю, что это прекрасно, когда локализовано в файле реализации. У меня есть то же правило, что и для using namespace - если это спасает меня от печатания и не смущает других, продолжай.

7 голосов
/ 28 апреля 2010

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

5 голосов
/ 28 апреля 2010

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

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

3 голосов
/ 28 апреля 2010

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

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

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

2 голосов
/ 28 апреля 2010

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

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

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

2 голосов
/ 28 апреля 2010

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

1 голос
/ 28 апреля 2010

Лично я нахожу, что typedefs очень полезны при работе с шаблонным кодом, как при написании шаблонов, так и при их создании.

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

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

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

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

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

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

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

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

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

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

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

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

Теперь представьте, что после написания вышеуказанного цикла for в 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, и любой экземпляр, который ссылается на него, автоматически изменяется.

0 голосов
/ 23 июня 2010

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

0 голосов
/ 29 апреля 2010

Если я хочу, чтобы конечный тип был строго типизирован, я обычно предпочитаю наследование, а не 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, тогда наследование - мой механизм выбора для "печатания".

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

...