Правильное использование 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 ведет себя как нормальное присвоение переменной...