Posts tagged ‘macroes’

if (false) { }

2010-05-22 16:49

Jeśli widzieliśmy już w swoim wystarczająco dużą ilość kodu w C lub C++, to istnieje pewna szansa, że natrafiliśmy na konstrukcję znaną jaką do-while(false). Występuje ona w dwóch wersjach. W pierwszej jest to właściwie goto w przebraniu – dzięki otoczeniu jakiegoś fragmentu kodu taką “pętlą” sprawiamy, że instrukcja break będzie powodowała skok tuż za nią. To pozwala na “wyjście” z danego fragmentu kodu np. wtedy, gdy wystąpi w nim jakiś błąd – oczywiście przy założeniu, że w środku nie ma kolejnej pętli lub switcha ;)
O drugiej wersji pisałem kilkanaście miesięcy temu. Jej zastosowaniem są makra preprocesora zawierająca kilka instrukcji; zamknięcie ich w do-while(false) zamiast w zwykły blok kodu ({ }) gwarantuje nam brak błędów składniowych niezależnie od miejsca użycia takiego makra.

Widać więc, że pętla z zawsze fałszywym warunkiem ma – mimo wszystko – jakieś zastosowanie… A co z analogicznym ifem? Czy if(false) ma jakieś sensowne zastosowanie poza ukazywaniem, że osoba używająca takiej konstrukcji do wyłączania kodu z kompilacji zapomniała o istnieniu dyrektywy #if, że o komentarzach nie wspomnę?…
Okazuje się, że jednak ma – przynajmniej mi dało się takowe zastosowanie znaleźć. Chcąc je zaprezentować, zacznę od takiego oto ciągu ifów:

  1. if (s == "foo") DoSth1(z);
  2. else if (s == "bar") DoSth2(x, y);
  3. else if (s == "baz") DoSth3();

który w założeniu może być oczywiście dowolnie długi i niekoniecznie musi też udawać switcha dla stringów. Rzecz w tym, aby każdy if miał podobną formę – na tyle podobną, że aż prosi się o jej zmakrowanie:

  1. #define C(val,code) else if (s == #val) { code; }

To by wyszło gdyby nie feler w postaci pierwszego ifa, który bynajmniej nie zaczyna się wcale od else‘a. Można jednak sprawdzić, żeby tak było. Jak?… Ano właśnie dzięki zawsze fałszywemu if(false):

  1. if (false) { }
  2. else if (s == "foo") DoSomething1(z);
  3. // ...

Robi on dokładnie nic, ale jednocześnie sprawia, że następujące dalej, prawdziwe ify mają dokładnie taką samą strukturę. Teraz już możemy je skrócić:

  1. #define C(val,code) else if (s==#val) {code;}
  2. if (false) { } C(foo,DoSth1(z)) C(bar,DoSth2(x,y)) C(baz,DoSth3())
  3. #undef C

Naturalnie dla trzech wariantów taka zabawa nie ma specjalnego sensu, ale dla dłuższej listy – czemu nie? Sam użyłem tej sztuczki parę razy, m.in. w parserze wyrażeń matematycznych, gdzie ciąg ifów wybierał wywołanie jednej z ponad 20 wbudowanych w niego funkcji. Nie jest to ładne – trzeba to przyznać – ale cóż, takie jest życie: czasami rozwiązania eleganckiego po prostu nie ma…

Tags: , ,
Author: Xion, posted under Programming » 3 comments

Kawałek kodu w makrze

2009-09-01 19:52

Jeśli staramy się pisać kod zgodnie z dobrymi praktykami programowania w C/C++, to niezbyt często korzystamy z dyrektywy #define. W większości przypadków ogranicza się to zresztą do zdefiniowania jakiegoś symbolu służącego do kompilacji warunkowej (np. WIN32_LEAN_AND_MEAN). Od czasu do czasu bywa jednak tak, że chcemy użyć #define w jego – teoretycznie – głównym zastosowaniu, czyli do zastępowania jednego kawałka kodu innym.

Uzasadnione przypadki takiego postępowania (do których nie należą np. makra udające funkcje, czyli sławetne #define min/max) to tylko takie sytuacje, w których bez #define rzeczywiście musielibyśmy wiele razy pisać (prawie) to samo. Moim ulubionym przykładem czegoś takiego jest wychodzenie z funkcji w wielu miejscach, gdzie przed każdym returnem musimy coś jeszcze zrobić – np.:

  1. bool SprobujCosObliczyc(const data& parametry, int* const wynik)
  2. {
  3.     if (parametryNieSaDobre) { *wynik = 0; return false; }
  4.     /* ... */
  5.     if (cosObliczylismyAleJestZle) { *wynik = 0; return false; }
  6.     /* ... */
  7.     if (tutajCosSiePopsulo) { *wynik = 0; return false; }
  8.     /* ... */
  9.     if (juzPrawieKoniecAleNiestetyLipa) { *wynik = 0; return false; }
  10.     /* ... */
  11.     return true;
  12. }

Zwyczajne ustawienie *wynik = 0; na początku funkcji będzie nieakceptowalne, jeśli chcemy, by w przypadku rzucenia wyjątku w którymś z /*... */ podana do funkcji zmienna pozostała bez zmian.
Prostym rozwiązaniem wydaje się więc makro:

  1. #define RETURN { *wynik = 0; return false; }

definiowane przed i #undefowane tuż po funkcji. Pomysł byłby dobry, gdyby nie to, że w tej postaci makro to powodowałoby błędy składniowe. Kiedy? Chociażby w takim ifie:

  1. if (wyjsc) RETURN; else { /* ... */ }

Tutaj bowiem kompilator nie znalazłby instrukcji if pasującej do else, co jest naturalnie błędem. Powodem tego jest średnik po wystąpieniu makra, generujący pustą instrukcję i sygnalizujący (przedwczesny) koniec klauzuli if.

Jak ominąć ten problem? Odpowiedź jest może zaskakująca: instrukcje trzeba zamknąć w… pętlę:

  1. #define RETURN do { *wynik = 0; return false; } while (0)

Jak nietrudno zauważyć, taka pętla wykona się dokładnie raz (i co więcej, kompilator o tym doskonale wie, zatem może ją zoptymalizować – tj. wyrzucić wszystko poza zawartością). Działanie jest więc takie same. Różnica polega na tym, że teraz takie makro może być używane w dowolnym miejscu bez żadnych niespodziewanych efektów.

Tags: , ,
Author: Xion, posted under Programming » 12 comments

Makra z wielokropkiem

2008-08-14 8:24

Chociaż nie jest zalecane, w C++ (podobnie jak w jego poprzedniku, C) możliwe jest wciąż tworzenie funkcji z nieokreśloną liczbą argumentów. Najbardziej znanym przykładem jest oczywiście printf:

  1. void printf(const char*, ...);

Nie wszyscy wiedzą jednak, że zmienną liczbą parametrów mogą mieć też makra (co nazywa się po angielsku variadic macros). Deklaruje się je wtedy w bardzo podobny sposób jak funkcje, też używając wielokropka:

  1. #define LOG(fmt, ...) fprintf(Logger::GetFile(), fmt, __VA_ARGS__);

Żeby jednak dostać się do ‘listy argumentów’ makra, używamy specjalnego identyfikatora __VA_ARGS__. W jego miejsce zostaną podstawione wszystkie podane parametry (z wyjątkiem pierwszego), oddzielone przecinkami tak, jak w oryginalnym “wywołaniu” makra.

Do czego może się to przydać? Jak widać powyżej, za pomocą takich makr możemy na przykład opakowywać wywołania funkcji ze zmienną liczbą argumentów – jak w powyższym przykładzie z logowaniem. Prawdopodobnie mogą być one przydatne także w implementacji jakiejś formy delegatów w C++, zwłaszcza jeśli chodzi o sposób ich wywoływania. Istnieje też ciekawa technika, pozwalająca na tworzenie (w C99 i późniejszych wersjach C/C++) funkcji z nieokreśloną liczbą argumentów, ale z zachowaną kontrolą ich typów.

Tags:
Author: Xion, posted under Programming » 8 comments
 


© 2023 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.