Макроси подібні до будь-якого іншого інструменту - молоток, який використовується у вбивстві, не є злом, бо це молоток. Це зло в тому, як людина використовує його таким чином. Якщо ви хочете забити цвяхи, молоток є ідеальним інструментом.
Є кілька аспектів макросів, які роблять їх "поганими" (я розгляну їх на кожному пізніше та запропоную альтернативні варіанти):
- Ви не можете налагоджувати макроси.
- Розширення макросів може призвести до дивних побічних ефектів.
- Макроси не мають "простору імен", тому, якщо у вас є макрос, який зіткнеться з іменем, яке використовується в іншому місці, ви отримуєте заміну макросів там, де цього не хотіли, і це зазвичай призводить до дивних повідомлень про помилки.
- Макроси можуть впливати на те, чого ви не уявляєте.
Тож давайте трохи розширимось тут:
1) Макроси неможливо налагодити.
Коли у вас є макрос, який перекладається на число або рядок, у вихідному коді буде ім’я макросу, а у багатьох налагоджувачів ви не зможете «побачити», на що перекладається макрос. Тож ви насправді не знаєте, що відбувається.
Заміна : Використовуйте enum
абоconst T
Для макросів, подібних до функцій, оскільки налагоджувач працює на рівні "для кожного рядка джерела, де ви знаходитесь", ваш макрос діятиме як єдиний оператор, незалежно від того, це один або сто. Важко зрозуміти, що відбувається.
Заміна : Використовуйте функції - вбудовані, якщо це потрібно "швидко" (але будьте обережні, що занадто багато вбудованих файлів - це не добре)
2) Розширення макросів може мати дивні побічні ефекти.
Знаменитий - це #define SQUARE(x) ((x) * (x))
і використання x2 = SQUARE(x++)
. Це призводить доx2 = (x++) * (x++);
, що, навіть якби це був дійсний код [1], майже напевно не був би тим, що хотів програміст. Якби це була функція, було б непогано зробити x ++, і x зростав би лише один раз.
Інший приклад - "if else" у макросах, скажімо, ми маємо таке:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
і потім
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Насправді це стає абсолютно неправильним ...
Заміна : реальні функції.
3) Макроси не мають простору імен
Якщо у нас є макрос:
#define begin() x = 0
і у нас є деякий код на C ++, який використовує begin:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
Тепер, яке повідомлення про помилку, на вашу думку, ви отримуєте, і де ви шукаєте помилку [припускаючи, що ви зовсім забули - або навіть не знали про неї - початковий макрос, який міститься в якомусь заголовковому файлі, який написав хтось інший? [і ще цікавіше, якби ви включили цей макрос перед включенням - ви б тонули в дивних помилках, які абсолютно не мають сенсу, коли ви дивитесь на сам код.
Заміна : Ну, тут існує не стільки заміна, скільки "правило" - використовуйте лише макроси у верхньому регістрі, і ніколи не використовуйте всі імена у верхньому регістрі для інших речей.
4) Макроси мають ефекти, яких ви не усвідомлюєте
Візьміть цю функцію:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
Тепер, не дивлячись на макрос, ви могли б подумати, що begin - це функція, яка не повинна впливати на x.
Такі речі, і я бачив набагато складніші приклади, ДІЙСНО можуть зіпсувати ваш день!
Заміна : Або не використовуйте макрос для встановлення x, або передайте x як аргумент.
Бувають випадки, коли використання макросів однозначно вигідно. Одним із прикладів є обгортання функції макросами для передачі інформації про файл / рядок:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Тепер ми можемо використовувати my_debug_malloc
як звичайний malloc в коді, але він має додаткові аргументи, тому, коли справа доходить до кінця і ми скануємо те, "які елементи пам'яті ще не звільнені", ми можемо надрукувати там, де було розподіл, так що програміст може відстежити витік.
[1] Невизначена поведінка оновлювати одну змінну більше одного разу "в точці послідовності". Точка послідовності - це не зовсім те саме, що твердження, але для більшості намірів і цілей саме це ми повинні розглядати. Таким чином x++ * x++
, оновлення буде оновлюватися x
двічі, що не визначено і, можливо, призведе до різних значень в різних системах, а також до різного значення результату x
.
#pragma
не є макросом.