Jeśli w C++ piszemy jakąś bibliotekę (albo nawet ogólniej: kod wielorazowego użytku), to zgodnie z dobrymi praktykami powinniśmy jej symbole zamknąć przynajmniej w jedną osobną przestrzeń nazw (namespace). Dzięki temu zapobiegniemy zminimalizujemy możliwość kolizji identyfikatorów w kodzie, który z naszego dzieła będzie korzystał – a także potencjalnie z innych bibliotek.
Nie wszystkie z nich jednak mogą być tak ładnie napisane – choćby dlatego, że któraś może być przeznaczona oryginalnie dla języka C. Najbardziej typowy przykład? Windows API. Dołączenie windows.h zasypuje globalną przestrzeń nazw istną lawiną symboli odpowiadających tysiącom funkcji czy typów zadeklarowanych w tym nagłówku. Nie jest to specjalnie dobre.
Jak temu zaradzić? Bardzo prostą, ale nierozwiązującą wszystkich problemów metodą jest stworzenie własnego nagłówka “opakowującego” ten biblioteczny w nową przestrzeń nazw:
Założenie jest takie, żeby wynikowego pliku nagłówkowego (foo.h) używać następnie w miejsce oryginalnego (foobar.h). Wtedy wszystkie symbole w nim zadeklarowane znajdą się wewnątrz nowej przestrzeni nazw, foo
.
Wszystkie?… Nie! Pakując kod napisany w stylu C bezpośrednio do przestrzeni nazw nie osiągniemy bowiem wszystkich celów, którym namespace‘y przyświecają. Owszem, da się co nieco poprawić: jeśli np. wspomniany windows.h zamknęlibyśmy w przestrzeni win
, to poniższy kod będzie jak najbardziej działał:
podczas gdy wersja bez przedrostków win::
już niezupełnie. Jednak nie jest to całkowity – nomen omen – win, bo z kolei takie wywołanie:
skutkuje już niestety failem :) Nasza przestrzeń nie może bowiem zamknąć wszystkiego, gdyż nie podlegają jej dyrektywy preprocesora – a w szczególności #define
. Pech polega na tym, że właśnie #define
jest w C podstawowym sposobem definiowania stałych, więc użyta wyżej nazwa SW_MINIMIZE
jest (w windows.h) określona po prostu jako:
Próba jej kwalifikowania powoduje zatem powstanie nieprawidłowego ciągu win::6
i słuszne narzekania kompilatora.
Nasz pojemnik (na nazwy) jest więc dziurawy i niestety nic z tym nie da się zrobić. Tak to już jest, gdy wciąż trzeba mieć do czynienia z API, które w tym przypadku liczy sobie – bagatelka – ponad 20 lat!
Ja polecalbym pojsc dalej i nawet kod zwyklej aplikacji pakowac w unikalna przestrzen. Co procz zapobieganou potencjalnych kolizji, uchroni nas przed tworzeniem dlugich potworkow w nazwach typow i obiektow.
Takie pakowanie ma przeogromną wadę — jeżeli wczytasz jakiś nagłówek to preprocesor doda już sobie flagę guarda do zdefiniowanych rzeczy. Zatem kolejny include w globalnej przestrzeni nazw nic nie zrobi.
Raz miałem styczność z tego typu problemem i był dość ciężki do wykrycia — ktoś przez przypadek w którymś z plików nagłówkowych zrobił include wewnątrz namespace. Przez to kilka plików cpp nie chciało się skompilować mimo tego, że widać było wszystkie wymagane include. Analizowanie pliku 7 MB (!) wyplutego przez preprocesor to żadna przyjemność. Ale takie już uroki wielkich projektów ;)
@revo: Oczywiście tak – zakładam, że nagłówek “unamespace’owiony” zastępuje tutaj pierwotny we wszystkich miejscach w danym projekcie. Jeśli tak nie jest, to rzeczywiście różne cuda mogą się zdarzyć :)
Ja tylko się zastanawiam, czy nawet jeśli udało by się, zapakować zawartość windows.h w namespace (lub inną bibliotekę bez wydzielonej przestrzeni nazw). To czy te wywołania były by poprawne? Znaczy się, czy nie burzył by się potem linker, tymi swoimi okrzykami unresolved external symbol… etc.