Makra są jak każde inne narzędzie - młotek użyty do morderstwa nie jest zły, ponieważ jest młotkiem. Sposób, w jaki człowiek używa go w ten sposób, jest zły. Jeśli chcesz wbijać gwoździe, młotek jest idealnym narzędziem.
Jest kilka aspektów, które sprawiają, że makra są „złe” (omówię każdy z nich później i zasugeruję alternatywy):
- Nie możesz debugować makr.
- Ekspansja makro może prowadzić do dziwnych efektów ubocznych.
- Makra nie mają „przestrzeni nazw”, więc jeśli masz makro, które koliduje z nazwą używaną w innym miejscu, możesz zastąpić makra tam, gdzie tego nie chcesz, co zwykle prowadzi do dziwnych komunikatów o błędach.
- Makra mogą wpływać na rzeczy, o których nie zdajesz sobie sprawy.
Rozwińmy więc trochę tutaj:
1) Makra nie mogą być debugowane.
Kiedy masz makro, które tłumaczy się na liczbę lub ciąg, kod źródłowy będzie miał nazwę makra, a wiele debuggerów, nie możesz „zobaczyć”, na co to makro tłumaczy. Więc tak naprawdę nie wiesz, co się dzieje.
Zamiennik : użyj enum
lubconst T
W przypadku makr „podobnych do funkcji”, ponieważ debugger działa na poziomie „na linię źródłową, na której się znajdujesz”, Twoje makro będzie zachowywać się jak pojedyncza instrukcja, bez względu na to, czy będzie to jedna instrukcja, czy sto. Utrudnia ustalenie, co się dzieje.
Zamiana : użyj funkcji - wbudowanych, jeśli ma być „szybka” (ale uważaj, że zbyt dużo wbudowanych nie jest dobrą rzeczą)
2) Rozszerzenia makro mogą mieć dziwne skutki uboczne.
Słynny jest #define SQUARE(x) ((x) * (x))
i zastosowanie x2 = SQUARE(x++)
. To prowadzi dox2 = (x++) * (x++);
czego, nawet gdyby był to prawidłowy kod [1], prawie na pewno nie byłby tym, czego chciał programista. Gdyby to była funkcja, byłoby dobrze zrobić x ++, a x zwiększyłby się tylko raz.
Innym przykładem jest „if else” w makrach, powiedzmy, że mamy to:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
i wtedy
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Właściwie staje się to zupełnie niewłaściwą rzeczą ...
Zastąpienie : prawdziwe funkcje.
3) Makra nie mają przestrzeni nazw
Jeśli mamy makro:
#define begin() x = 0
i mamy kod w C ++, który używa 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;
Jaki komunikat o błędzie myślisz, że otrzymujesz i gdzie szukasz błędu [zakładając, że całkowicie zapomniałeś - lub nawet o nim nie wiedziałeś - makro begin, które znajduje się w jakimś pliku nagłówkowym, który napisał ktoś inny? [a nawet fajniejsze, gdybyś dołączył to makro przed włączeniem - utonąłbyś w dziwnych błędach, które nie mają absolutnie żadnego sensu, gdy spojrzysz na sam kod.
Zastąpienie : Cóż, nie jest to zamiennik, ale „reguła” - używaj tylko nazw wielkich liter dla makr i nigdy nie używaj nazw wielkich liter do innych rzeczy.
4) Makra mają efekty, o których nie zdajesz sobie sprawy
Weź tę funkcję:
#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();
}
Teraz, bez patrzenia na makro, można by pomyśleć, że begin to funkcja, która nie powinna wpływać na x.
Takie rzeczy, a widziałem o wiele bardziej złożone przykłady, mogą NAPRAWDĘ zepsuć twój dzień!
Zamiana : albo nie używaj makra do ustawienia x, albo przekaż x jako argument.
Są chwile, kiedy używanie makr jest zdecydowanie korzystne. Jednym z przykładów jest zawijanie funkcji makrami w celu przekazania informacji o pliku / wierszu:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Teraz możemy użyć my_debug_malloc
w kodzie zwykłego malloc, ale ma on dodatkowe argumenty, więc kiedy dojdzie do końca i zeskanujemy „które elementy pamięci nie zostały zwolnione”, możemy wydrukować, gdzie dokonano alokacji, więc programista może wyśledzić wyciek.
[1] Niezdefiniowanym zachowaniem jest aktualizowanie jednej zmiennej więcej niż raz „w punkcie sekwencji”. Punkt sekwencji nie jest dokładnie tym samym, co stwierdzenie, ale w większości intencji i celów właśnie za to powinniśmy go uważać. W ten sposób x++ * x++
zaktualizujemy x
dwukrotnie, co jest niezdefiniowane i prawdopodobnie doprowadzi do różnych wartości w różnych systemach, a także do różnych wartości wyniku x
.
#pragma
nie jest makro.