Aby odpowiedzieć na pytania, zastanów się, do jakich makr używa się głównie (Ostrzeżenie: kod skompilowany z mózgiem).
- Makra używane do definiowania stałych symbolicznych
#define X 100
Można to łatwo zastąpić: const int X = 100;
- Makra używane do definiowania (zasadniczo) wbudowanych funkcji agnostycznych
#define max(X,Y) (X>Y?X:Y)
W każdym języku, który obsługuje przeciążanie funkcji, można to emulować w sposób znacznie bardziej bezpieczny dla typu, przeciążając funkcje poprawnego typu lub, w języku obsługującym funkcje ogólne, funkcją ogólną. Makro chętnie spróbuje porównać wszystko, w tym wskaźniki lub ciągi, które mogą się skompilować, ale prawie na pewno nie jest to, czego chciałeś. Z drugiej strony, jeśli makra są bezpieczne dla typu, nie oferują żadnych korzyści ani wygody w przypadku przeciążonych funkcji.
- Makra używane do określania skrótów do często używanych elementów.
#define p printf
Można to łatwo zastąpić funkcją, p()
która robi to samo. Jest to dość zaangażowane w C (wymagający użycia va_arg()
rodziny funkcji), ale w wielu innych językach, które obsługują zmienną liczbę argumentów funkcji, jest to o wiele prostsze.
Obsługa tych funkcji w języku zamiast w specjalnym języku makr jest prostsza, mniej podatna na błędy i znacznie mniej myląca dla osób czytających kod. W rzeczywistości nie mogę wymyślić jednego przypadku użycia makr, którego nie można łatwo powielić w inny sposób. Tylko miejsce, gdzie makra są naprawdę przydatna jest, gdy są one związane z konstrukcjami warunkowego składankach takich jak #if
(itd.).
W tej kwestii nie będę się z tobą kłócił, ponieważ uważam, że niepreprocesorowe rozwiązania kompilacji warunkowej w popularnych językach są wyjątkowo kłopotliwe (jak wstrzykiwanie kodu bajtowego w Javie). Ale języki takie jak D wymyślają rozwiązania, które nie wymagają preprocesora i nie są bardziej kłopotliwe niż stosowanie warunkowych preprocesorów, a jednocześnie są znacznie mniej podatne na błędy.