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
Tagi: , ,
Autor: Xion w Programowanie »


Możesz śledzić komentarze do tej notki poprzez kanał RSS 2.0.
Możesz przejść do końca i zostawić komentarz. Śledzenie notek (trackback) jest aktualnie wyłączone.


12 komentarze/y do notki “Kawałek kodu w makrze”.
  1. lukaszw:
    Wrzesień 1st, 2009 o 21:18

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

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

  2. Xion:
    Wrzesień 1st, 2009 o 23:49

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

  3. yarpen:
    Wrzesień 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:
    Wrzesień 2nd, 2009 o 17:29

    A co z potepianym goto? Tutaj chyba dobrze pasowaloby.

  5. Aithne:
    Wrzesień 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:
    Wrzesień 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:
    Wrzesień 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:
    Wrzesień 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:
    Wrzesień 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:
    Wrzesień 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:
    Październik 6th, 2009 o 1:33

    A co z potepianym goto? Tutaj chyba dobrze pasowaloby.

  12. Groshu:
    Październik 13th, 2009 o 23:12

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

Dodaj komentarz

Znaki nowej linii dodawane są automatycznie.
Do wstawiania kodu użyj [code][/code], a do wzorów (w LaTeX-u) [tex][/tex].
Dozwolne tagi HTML:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 



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