Historia kwestii:
Makra pochodzą z języka C jako potężny sposób automatyzacji powtarzających się fragmentów kodu na etapie preprocesora. W C++ ich użycie dało elastyczność, ale również przyniosło wiele ukrytych niebezpieczeństw z powodu braku kontroli typów i nieoczywistego działania preprocesora.
Problem:
Główne ryzyka związane z używaniem makr:
Rozwiązanie:
W nowoczesnych standardach C++ zaleca się używanie funkcji inline, szablonów, constexpr, enum class oraz zmiennych constexpr zamiast makr.
Przykład kodu:
// Źle: #define MAX(a, b) ((a) > (b) ? (a) : (b)) // Dobrze: template<typename T> constexpr T max(T a, T b) { return a > b ? a : b; }
Kluczowe cechy:
Czy makro może być bardziej niebezpieczne niż funkcja inline?
Tak. Makro nie podlega zasadom składni i typów. Możliwy jest niespodziewany wynik przy przekazywaniu parametrów z efektami ubocznymi.
#define SQUARE(x) ((x) * (x')) int y = 5; int z = SQUARE(y++); // y inkrementowane dwa razy!
Czy #include jest również makrem?
Nie, #include to dyrektywa preprocesora, ale użycie makr i include jest związane: przez makro można zmieniać listę dołączanych plików (co jest skrajnie niezalecane).
Czy można debugować makro jak zwykłą funkcję?
Nie, debugger rozwija makro i pokazuje już podstawiony tekst, nie ma oddzielnych nazwanych bytów.
W starym kodzie zdefiniowano liczne makra obliczeniowe z efektami (np. inkrementacja), co prowadziło do trudnych do uchwycenia błędów podczas eksploatacji nowych funkcji.
Zalety:
Wady:
Podczas refaktoryzacji makra zostały zastąpione funkcjami szablonowymi i constexpr, a enum class zastosowano zamiast flag makr.
Zalety:
Wady: