Notki oznaczone tagiem ‘makra’

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:

if (s == "foo") DoSth1(z);
else if (s == "bar") DoSth2(x, y);
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:

#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):

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

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ć:

#define C(val,code) else if (s==#val) {code;}
if (false) { } C(foo,DoSth1(z)) C(bar,DoSth2(x,y)) C(baz,DoSth3())
#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...

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks

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.:

bool SprobujCosObliczyc(const data& parametry, int* const wynik)
{
    if (parametryNieSaDobre) { *wynik = 0; return false; }
    /* ... */
    if (cosObliczylismyAleJestZle) { *wynik = 0; return false; }
    /* ... */
    if (tutajCosSiePopsulo) { *wynik = 0; return false; }
    /* ... */
    if (juzPrawieKoniecAleNiestetyLipa) { *wynik = 0; return false; }
    /* ... */
    return true;
}

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:

#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:

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ę:

#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.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks

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:

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:

#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.

  • RSS
  • Facebook
  • Twitter
  • Wykop
  • Reddit
  • del.icio.us
  • Google Bookmarks
Tagi:
Autor: Xion w Programowanie » Komentarze (8)
 



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