niedziela, lutego 01, 2009

Evil macro magic

Ostatnio w zobaczyłem ciekawy fragment kodu (C++):

#include "settings.hpp"
/* evil macro magic */
#undef _SETTINGS_HPP
#define extern
#include "settings.hpp"
#undef extern

Ciężko mi skomentować tak ciężkie nadużycie - które z drugiej strony jest genialne :-) Wyjaśnienie: powyższe linijki sprawiają, że wszystkie deklaracje "extern" zmiennych w "settings.hpp" stają się zwykłymi deklaracjami. Może to prowadzić do bardzo nieprzyjemnych błędów, ale zapewne czasami jest to użyteczne.

3 komentarze:

  1. Anonimowy4:36 PM

    Ktoś po prostu napotkał na taki problem, że jak dał definicje w pliku nagłówkowym, który był włączany w różnych jednostkach translacji to dostawał błędy od linkera (w końcu każda zmienna ma wówczas kilka kopii).

    To nie jest genialne pod żadnym względem. Ewidentnie jest to napisane z braku wiedzy.

    Jest to rozwiązanie nieskalowalne - nie można nawet mieć ustawień typu bez konstruktora domyślnego.

    Jest to w C++, więc należałoby zrobić klasę ustawień i ewentualnie dać ją jako singleton.

    Pomijając jednak to trywialne i narzucające się rozwiązanie można również w pliku nagłówkowym dać wszystkie definicje jako const. W końcu są to ustawienia, więc nie będą modyfikowane w czasie działania (tak, to wystarczy aby linker się nie rzucał).

    Jeżeli to nie wystarcza (i piszemy w C) to należy zrobić plik settings.cpp w którym są wszystkie definicje zmiennych (i są statyczne dla tego modułu). Z zewnątrz odwołujemy się do ustawień poprzez funkcje, które są exportowane przez settings.h.

    Pomijając fakt, że ustawienia są po to aby były wczytywane z pliku ;-P

    OdpowiedzUsuń
  2. Nie zgadzam się z Twoją interpetacją: zauważ, że settings.hpp wyraźnie *MA* zabezpieczenie przed wielokrotnym includowaniem. A osoba która to pisała wie jak działa linker ;-) To co tutaj mamy to unikanie deklarowania zmiennych dwa razy: dzięki temu mamy tylko jedno miejsce gdzie musimy zmieniać typ zmiennej, dajmy na to. Oczywiście jest tu mowa o zmiennych globalnych, co do których można mieć wiadome zastrzeżenia, ale w pewnych przypadkach ich istnienie jest zrozumiałe - wręcz porządane.

    >>> Pomijając jednak to trywialne i narzucające się rozwiązanie można również w pliku nagłówkowym dać wszystkie definicje jako const. W końcu są to ustawienia, więc nie będą modyfikowane w czasie działania (tak, to wystarczy aby linker się nie rzucał).

    To absolutnie nie jest prawda. Co innego magiczne stałe zaszyte w programie, a co innego stan programu.

    OdpowiedzUsuń
  3. Doskonale rozumiem ten kod. Mam poważne zastrzeżenia do samej koncepcji tego "czegoś". Nie chodzi o zmienne globalne.

    > zauważ, że settings.hpp wyraźnie *MA* zabezpieczenie
    > przed wielokrotnym includowaniem.
    Właśnie o tym braku wiedzy pisałem. Trzeba jeszcze wiedzieć jak takie zabezpieczenie działa. Daj do pliku settings.hpp dyrektywę #warning WLACZANIE KODU.
    To wypisze sie tyle razy ile plik bedzie wlaczany. Na kazda jednostke translacji raz. Jezeli dasz pliki a.cpp i b.cpp, które włączają settings.hpp i skompilujesz g++ a.cpp b.cpp to niezależnie czy masz zabezpieczenie czy nie, włączysz kod dwa razy (warning się pokaże dwa razy). Dlatego linker będzie szalał gdy w pliku settings.h będzie *definicja* zmiennej, która nie jest stałą. Tak samo z funkcją czy metodą, która nie jest "inline".

    > Co innego magiczne stałe zaszyte w programie, a co
    > innego stan programu.
    Myślałem, że mówimy o ustawieniach programu (czyli grupie "magicznych stałych"). Warto dla nich zrobić osobną klasę (lub moduł w C, do którego dostęp jest przez funkcje) choćby po to aby w przyszłości dało się zmienić sposób pamiętania ustawień (np. wywalić to do pliku konfiguracyjnego).

    OdpowiedzUsuń