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.

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


12 comments for post “Kawałek kodu w makrze”.
  1. lukaszw:
    September 1st, 2009 o 21:18

    #define RETURN return((*wynik=0)!=0)

    !=0 dla uniknięcia warning'a :P

  2. Xion:
    September 1st, 2009 o 23:49

    W tym specyficznym przypadku tak. W ogólności niekoniecznie ;]

  3. yarpen:
    September 2nd, 2009 o 9:59

    Generalnie jezeli odczuwa sie pokuse uzycia makra, to nalezy przemyslec design. W tym przypadku wystarczy RAII. (ew. *wynik = 0 na samym poczatku f-kcji).

  4. Malcom:
    September 2nd, 2009 o 17:29

    A co z potepianym goto? Tutaj chyba dobrze pasowaloby.

  5. Aithne:
    September 2nd, 2009 o 21:23

    Zamiast potępianego goto użyłeś więc jeszcze bardziej potępianych makr. Super. Tak to jest, jak się wbije do głowy, że goto jest zawsze złe. A kombinowanie z RAII to w tym przypadku Little Boy na muchę.

  6. Xion:
    September 3rd, 2009 o 1:10

    Eee... Czy wszyscy umyślnie ignorują ten mały skrót "np."? Tak, to był tylko przykład, w którym *można* chcieć użyć makr do prostego copy-paste fragmentów kodu - ale oczywiście nie trzeba. Chciałem tylko pokazać, jak wtedy zrobić to poprawnie, i tyle.

  7. Aithne:
    September 3rd, 2009 o 13:01

    Co nie zmienia faktu, że dla takiego podstawiania najczęściej można użyć goto - przynajmniej się nie można nadziać na jakąś literówkę w makrze i zastanawiać się, czemu mamy milion błędów.

  8. Reg:
    September 6th, 2009 o 18:46

    Ale dziwna kombinacja :) Ciekawa, ale to kolejny dowód na to jak pokrętny jest C++. Moim zdaniem lepiej po prostu użycia takich makr obejmować w swoim kodzie w nawiasy klamrowe, tak jak if { MAKRO; } else ..., albo świadomie nie pisać tego średnika po użyciu makra.

  9. Syriusz:
    September 8th, 2009 o 22:25

    Hm, ja staram się stosować wzorzec pojedynczego wyjścia z funkcji (nie pamiętam ja się to dokładnie nazywało). I chyba miałby zastosowanie w powyższym przykładzie:
    {
    bool result = true;
    result = parametryNieSaDobre;
    if(result)
    {
    /* ... */
    result = cosObliczylismyAleJestZle;
    }
    if(result)
    {
    /* ... */
    result = tutajCosSiePopsulo;
    }
    if(result)
    {
    /* ... */
    result = juzPrawieKoniecAleNiestetyLipa;
    }
    if(!result)
    *wynik = 0;
    return result;
    }

  10. siso:
    September 29th, 2009 o 1:49

    Jeżeli chcemy, by w obliczu wyjątku przekazana zmienna pozostała bez zmian, a w pozostałych przypadkach chcemy zwrócić z funkcji wiele wartości, można ją przekazać do funkcji przez, hmmm... wartość (zamiast przez wskaźnik), zaś zwracać z funkcji krotkę (tuple) z odpowiednio ustawionymi składowymi.

  11. RV:
    October 6th, 2009 o 1:33

    A co z potepianym goto? Tutaj chyba dobrze pasowaloby.

  12. Groshu:
    October 13th, 2009 o 23:12

    wzorzec pojedynczego wyjścia z funkcji w tym przypadku służył by głównie spowanlianiu kodu :p

Add a comment

Newline tags are added automatically.
For code, use [code][/code]. You can also insert LaTeX formulae inside [tex][/tex].
HTML tags allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 


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