typedefs та #defines


32

Усі ми однозначно використовували typedefs і #defines той чи інший час. Сьогодні, працюючи з ними, я почав розмірковувати над річчю.

Розглянемо наведені нижче 2 ситуації для використання intтипу даних з іншим іменем:

typedef int MYINTEGER

і

#define MYINTEGER int

Як і вищезгадана ситуація, у багатьох ситуаціях ми можемо дуже добре виконати річ за допомогою #define, а також зробити те ж саме, використовуючи typedef, хоча способи, яким ми робимо те саме, можуть бути зовсім різними. #define також може виконувати дії MACRO, які typedef не може.

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


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

Відповіді:


67

Як 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;

(Typedefs для типів вказівників зазвичай не є гарною ідеєю, але це ілюструє суть.)

Ще один дивний випадок:

#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. Крім того, це більш зрозуміло семантично. Коли хтось, хто читає ваш код, побачить визначення, він не буде знати про що йдеться, поки він не прочитає його і не зрозуміє весь макрос. Typedef повідомляє читачеві, що буде визначено ім'я типу. (Я мушу зазначити, що я говорю про C ++. Я не впевнений у пошуку типу typedef для C, але я думаю, що це схоже).


Але як я показав у прикладі, наведеному у запитанні, typedef та #define використовують однакову кількість слів! Крім того, ці 3 слова макросу не спричинить заплутаності, оскільки слова однакові, але просто переставлені. :)
c0da

2
Так, але справа не в короткості. Макрос може робити ще мільйон інших речей, крім набору тексту. Але особисто я вважаю, що скопіювання є найважливішим.
Tamás Szelei

2
@ c0da - ви не повинні використовувати макроси для typedefs, ви повинні використовувати typedefs для typedefs. Дійсний ефект макросу може бути дуже різним, що проілюстровано різними відповідями.
Joris Timmermans

15

Вихідний код в основному написаний для інших розробників. Комп'ютери використовують компільовану його версію.

У цій перспективі typedefє сенс, #defineякий не відповідає.


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

Крім звичайних аргументів проти визначення, як би ви написали цю функцію за допомогою макросів?

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 у стандартних контейнерах дозволяє загальній функції (на зразок тієї, яку я створив вище) для посилання на ці типи. Функція "Сума" шаблонна на тип контейнера ( std::vector<int>), а не на тип, який міститься всередині контейнера ( int). Без typedef не можна було б посилатися на цей внутрішній тип.

Тому typedefs є центральними для Modern C ++, і це не можливо для макросів.


Питання про макроси vs typedef, а не макроси проти вбудованих функцій.
Бен Войгт

Зрозуміло, і це можливо лише для typedefs ... саме таким є value_type.
Кріс Пітман

Це не typedef, це шаблонне програмування. Функція чи функтор не є типом, незалежно від того, наскільки він загальний.

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

Досить справедливо. Можливо, було б краще розмістити лише другий фрагмент.

1

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

Коли ви створюєте новий тип, ви визначаєте нову "річ", якою домен вашої програми маніпулює. Таким чином, ви можете використовувати цю "річ" для складання функцій та класів, а ви можете використовувати компілятор для вашої користі, щоб робити статичні перевірки. У будь-якому випадку, оскільки C ++ сумісний із C, існує безліч неявних перетворень між intтипами та на основі int, які не генерують попереджень. Таким чином, ви не отримаєте повну потужність статичних перевірок у цьому випадку. Однак ви можете замінити свій typedefна, enumщоб знайти неявні конверсії. Напр .: Якщо у вас є typedef int Age;, то ви можете замінити на них, enum Age { };і ви отримаєте всі види помилок при неявних перетвореннях між Ageі int.

Інша справа: typedefможе бути всередині namespace.


1

Використання визначень замість typedefs викликає занепокоєння під іншим важливим аспектом, а саме концепцією ознак типу. Розглянемо різні класи (подумайте про стандартні контейнери), які визначають конкретні типи. Ви можете написати загальний код, посилаючись на typedefs. Приклади цього включають загальні вимоги до контейнера (c ++ стандарт 23.2.1 [container.requirements.general]), наприклад

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

Все це не можна виразити в загальному вигляді з макросом, тому що воно не має масштабу.


1

Не забувайте про наслідки цього у налагоджувачі. Деякі налагоджувачі не справляються з #define дуже добре. Пограйте з обома в налагоджувачі, який ви використовуєте. Пам’ятайте, ви витратите більше часу на її читання, ніж на його написання.


-2

"#define" замінить те, що ви написали після, typedef створить тип. Тож якщо ви хочете мати тип зберігання - використовуйте typedef. Якщо вам потрібні макроси - використовуйте define.


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