typedefs и #defines

32

Мы все определенно использовали typedefs и #defines один или другой раз. Сегодня, работая с ними, я начал задумываться о чем-то.

Рассмотрим следующие 2 ситуации для использования intтипа данных с другим именем:

typedef int MYINTEGER

а также

#define MYINTEGER int

Как и в приведенной выше ситуации, во многих ситуациях мы можем очень хорошо выполнить задачу, используя #define, а также сделать то же самое, используя typedef, хотя способы, которыми мы делаем то же самое, могут быть совершенно разными. #define также может выполнять действия MACRO, которые typedef не может.

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

c0da
источник
8
Используйте #define для чего они предназначены. Условная компиляция основана на свойствах, которые вы не можете знать при написании кода (например, OS / Compiler). В противном случае используйте языковые конструкции.
Мартин Йорк,

Ответы:

67

A typedefобычно предпочтительнее, если нет какой-то странной причины, по которой вам нужен макрос.

макросы выполняют текстовую подстановку, что может существенно нарушить семантику кода. Например, учитывая:

#define MYINTEGER int

Вы могли бы на законных основаниях написать:

short MYINTEGER x = 42;

потому что short MYINTEGERрасширяется до short int.

С другой стороны, с typedef:

typedef int MYINTEGER:

имя MYINTEGER- это другое имя для типа int , а не текстовая подстановка для ключевого слова "int".

Вещи становятся еще хуже с более сложными типами. Например, учитывая это:

typedef char *char_ptr;
char_ptr a, b;
#define CHAR_PTR char*
CHAR_PTR c, d;

a, bИ cвсе указатели, но dэто char, так как последняя линия расширяется:

char* c, d;

что эквивалентно

char *c;
char d;

(Определение типов для указателей обычно не очень хорошая идея, но это иллюстрирует суть.)

Еще один странный случай:

#define DWORD long
DWORD double x;     /* Huh? */
Кит Томпсон
источник
1
Указатели снова виноваты! :) Любой другой пример, кроме указателей, где неразумно использовать такие макросы?
c0da
2
Не то, что я когда - либо использовать макрос вместо typedef, но правильный способ создать макрос , который делает что - то с тем, что следует это использовать аргументы: #define CHAR_PTR(x) char *x. Это, по крайней мере, приведет к тому, что компилятор будет некорректно использоваться.
Blrfl
1
Не забывайте:const CHAR_PTR mutable_pointer_to_const_char; const char_ptr const_pointer_to_mutable_char;
Джон Пурди
3
Определяет также сделать сообщения об ошибках непонятными
Томас Бонини
Пример боли, которую может вызвать неправильное использование макросов (хотя это и не имеет прямого отношения к макросам против typedefs): github.com/Keith-S-Thompson/42
Кит Томпсон,
15

Безусловно, самая большая проблема с макросами заключается в том, что они не имеют границ. Это само по себе гарантирует использование typedef. Кроме того, это более понятно семантически. Когда кто-то, кто читает ваш код, видит определение, он не будет знать, о чем он, пока не прочитает его и не поймет весь макрос. Определение типа сообщает читателю, что имя типа будет определено. (Я должен упомянуть, что я говорю о C ++. Я не уверен насчет определения типа typedef для C, но я думаю, что это похоже).

Тамас Селеи
источник
Но, как я показал в примере, приведенном в вопросе, typedef и #define используют одинаковое количество слов! Кроме того, эти 3 слова макроса не вызовут путаницы, поскольку слова одинаковы, но просто переставлены. :)
c0da
2
Да, но это не о краткости. Макрос может делать миллионы других вещей, кроме определения типа. Но лично я считаю, что обзор важнее всего.
Тамас Селей
2
@ c0da - вы не должны использовать макросы для typedefs, вы должны использовать typedefs для typedefs. Фактический эффект макроса может быть очень разным, о чем свидетельствуют различные ответы.
Йорис Тиммерманс
15

Исходный код в основном написан для коллег-разработчиков. Компьютеры используют скомпилированную версию.

В этой перспективе typedefесть значение, #defineкоторого нет.

mouviciel
источник
6

Ответ Кейта Томпсона очень хороший, и вместе с дополнительными замечаниями Тамаса Селеи о сфере охвата вы должны получить всю необходимую информацию.

Вы всегда должны считать макросы самым последним средством отчаянного. Есть определенные вещи, которые вы можете делать только с макросами. Даже тогда вы должны долго и усердно думать, если вы действительно хотите это сделать. Объем отладки, который может быть вызван слегка испорченными макросами, значителен, и вы будете просматривать предварительно обработанные файлы. Стоит сделать это только один раз, чтобы почувствовать масштабы проблемы в C ++ - только размер предварительно обработанного файла может быть откровением.

Йорис Тиммерманс
источник
5

В дополнение ко всем действительным замечаниям о замене области видимости и текста, #define также не полностью совместим с typedef! А именно, когда дело доходит до указателей на функции.

typedef void(*fptr_t)(void);

Это объявляет тип, fptr_tкоторый является указателем на функцию типа void func(void).

Вы не можете объявить этот тип с помощью макроса. #define fptr_t void(*)(void)очевидно, не будет работать. Вам придется написать что-то непонятное #define fptr_t(name) void(*name)(void), что на самом деле не имеет никакого смысла в языке Си, где у нас нет конструкторов.

Указатели массива также нельзя объявить с помощью #define: typedef int(*arr_ptr)[10];


И хотя в C нет языковой поддержки безопасности типов, о которой стоит упомянуть, другой случай, когда typedef и #define несовместимы, - это когда вы делаете подозрительные преобразования типов. Компилятор и / или инструмент астатического анализатора могут выдавать предупреждения для таких преобразований, если вы используете typedef.


источник
1
Еще лучше использовать комбинации указателей на функции и массивы, например char *(*(*foo())[])();( fooфункция, возвращающая указатель на массив указателей на функции, возвращающие указатель на char).
Джон Боде
4

Используйте инструмент с наименьшим количеством энергии, который выполняет работу, и тот, который содержит большинство предупреждений. #define оценивается в препроцессоре, вы там в основном сами по себе. typedef оценивается компилятором. Проверки даны, и typedef может определять только типы, как следует из названия. Так что в вашем примере определенно перейдите на typedef.

Мартин
источник
2

Помимо обычных аргументов против define, как бы вы написали эту функцию с помощью макросов?

template <typename IterType>
typename IterType::value_type Sum(
    const IterType& begin, 
    const IterType& end, 
    const IterType::value_type& initialValue)
{
    typename IterType::value_type result = initialValue;
    for (IterType i = begin; i != end; ++i)
        result += i;

    return result;
}

....

vector<int> values;
int sum = Sum(values.begin(), values.end(), 0);

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

* Я только что написал это здесь, я оставляю компиляцию в качестве упражнения для читателя :-)

РЕДАКТИРОВАТЬ:

Этот ответ, кажется, вызывает много путаницы, поэтому позвольте мне сделать это подробнее. Если бы вы заглянули внутрь определения вектора STL, вы бы увидели нечто похожее на следующее:

template <typename ValueType, typename AllocatorType>
class vector
{
public:
    typedef ValueType value_type;
...
}

Использование typedefs в стандартных контейнерах позволяет универсальной функции (например, той, которую я создал выше) ссылаться на эти типы. Функция Sum суммируется с типом контейнера ( std::vector<int>), а не с типом, содержащимся в контейнере ( int). Без typedef было бы невозможно сослаться на этот внутренний тип.

Следовательно, typedefs являются центральными для Modern C ++, а это невозможно с макросами.

Крис Питман
источник
Вопрос, заданный о typedefмакросах против встроенных функций, а не макросах.
Бен Фойгт
Конечно, и это возможно только с typedefs ... вот что такое value_type.
Крис Питман
Это не определение типа, это программирование шаблонов. Функция или функтор не является типом, независимо от того, насколько он универсален.
@Lundin Я добавил дополнительные детали: функция Sum возможна только потому, что стандартные контейнеры используют typedef для представления типов, для которых они созданы. Я не говорю, что Sum - это typedef. Я говорю, что std :: vector <T> :: value_type является typedef. Не стесняйтесь смотреть на источник любой стандартной реализации библиотеки, чтобы проверить.
Крис Питман
Справедливо. Возможно, было бы лучше опубликовать только второй фрагмент.
1

typedefсоответствует философии C ++: все возможные проверки / утверждения во время компиляции. #defineэто просто прием препроцессора, который скрывает много семантики для компилятора. Вам не следует беспокоиться о производительности компиляции больше, чем о правильности кода.

Когда вы создаете новый тип, вы определяете новую «вещь», которой манипулирует домен вашей программы. Таким образом, вы можете использовать эту «вещь» для составления функций и классов, и вы можете использовать компилятор для своей выгоды для выполнения статических проверок. В любом случае, поскольку C ++ совместим с C, существует много неявных преобразований между intтипами и типами, основанными на int, которые не генерируют предупреждения. Таким образом, вы не получите полную мощность статических проверок в этом случае. Тем не менее, вы можете заменить свой typedefна, enumчтобы найти неявные преобразования. Например: если вы typedef int Age;, то вы можете заменить на, enum Age { };и вы получите все виды ошибок путем неявных преобразований между Ageи int.

Другое дело: typedefможет быть внутри namespace.

dacap
источник
1

Использование определений вместо typedefs беспокоит еще один важный аспект, а именно концепция черт типа. Рассмотрим различные классы (например, стандартные контейнеры), каждый из которых определяет свои конкретные определения типов. Вы можете написать общий код, ссылаясь на typedefs. Примеры этого включают общие требования к контейнерам (стандарт c ++ 23.2.1 [container.requirements.general]), например

X::value_type
X::reference
X::difference_type
X::size_type

Все это не может быть выражено в общем виде с помощью макроса, потому что он не ограничен.

Francesco
источник
1

Не забывайте последствия этого в вашем отладчике. Некоторые отладчики не очень хорошо справляются с #define. Поиграйте с обоими в отладчике, который вы используете. Помните, что вы потратите больше времени на чтение, чем на написание.

скоро
источник
-2

«#define» заменит то, что вы написали после, typedef создаст тип. Так что если вы хотите иметь привычный тип - используйте typedef. Если вам нужны макросы - используйте define.

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