Zdarza się, że pracuje nad złożonym systemem, na który składa się kilka osobnych projektów. IDE znają dobrze takie przypadki i potrafią je obsługiwać – stąd chociażby pojęcie solution (dawniej workspace) w Visual Studio. Dla pojedynczych aplikacji i bibliotek wydaje się ono zbędne, jednak staje się nieodzowne wtedy, gdy nasze projekty zależą od siebie.
Typowa sytuacja to wspólna biblioteka (framework, engine czy co jeszcze kto woli) rozwijana razem z programami, które z niej korzystają. (W najprostszym przypadku to może być po prostu jakaś aplikacja testowa). Wówczas pojawiają się zależności między projektami na etapie ich budowania: wynik szeroko pojętej “kompilacji” jednego jest wejściem do procesu budowania innego. Jeśli nie poświęcimy temu faktowi należytej uwagi, to mogą nas czekać kłopoty. W najlepszym razie jest to konieczność wciskania F7 (Build Solution) więcej niż raz, aż do zbudowania wszystkich projektów. W gorszym – uruchamianie (i debugowanie!) aplikacji korzystającej z nieaktualnej, bo nieprzekompilowanej wersji biblioteki.
Zależności między projektami w procesie budowania da się na szczęście określić. W Visual Studio służy do tego opcja Project Dependencies z menu – a jakże – Project. Możemy w niej określić dla każdego projektu, z jakimi innymi projektami z tego samego solution jest on powiązany, czyli które z nich powinny być już wcześniej od niego zbudowane. Na podstawie tak podanej sieci zależności da się następnie określić właściwą kolejności “kompilacji” dla wszystkich projektów w danym solution. VS oczywiście to czyni, używając do tego zapewne sortowania topologicznego w analogiczny sposób jak dla kompilacji jednego projektu składającego się z wielu plików.
Napiszę dzisiaj o nierzadko zapominanej klasie z biblioteki standardowej C++, czyli o tytułowym zbiorze bitów – std::bitset
. Działania na pojedynczych bitach nie są raczej czymś, co wykonujemy codziennie, ale zdarzają się sytuacje, gdy stanowią one najrozsądniejsze rozwiązanie w danej chwili. Takim typowym przypadkiem jest zbiór flag logicznych: kilku opcji działających jak przełączniki, których możliwą wartością jest tylko true
albo false
. Wszelkie API doskonale zna takie konstrukcje, czego przykładem może być metoda Clear
urządzenia DirectX:
D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER
jest właśnie kombinacją flag bitowych, a dwie występujące tu stałe zostały odpowiednio zdefiniowane, by dało się je łączyć operatorem sumy bitowej |
.
Używając std::bitset
możemy nieco uprościć obsługę takich opcji. Zamiast deklarowania specjalnych wartości, możemy użyć zwykłego enum
a, np.:
bitset
jest wtedy łatwy w użyciu:
Zachowana jest przy tym “kompatybilność wsteczna” w stosunku do ręcznie definiowanych stałych, jeśli chcielibyśmy z nich korzystać:
Na koniec wspomnę tylko, że aby korzystać z klasy bitset
do tych lub innych zastosowań, należy wpierw dołączyć standardowy nagłówek o nazwie… bitset :)
Ponieważ programowaniem zajmuję się już od dość długiego czasu, mam w tym nieco doświadczenia praktycznego. Przez ten czas zdążyłem też zmienić część swoich poglądów na niektóre rzeczy z tym związane. Mówiąc trochę górnolotnie: stałem się bogatszy o wiedzę z życia wziętą :)
Jedną z takich nauczek życiowych jest to, by nie ufać zbytnio standardom, zaleceniom i innym tzw. dobrym praktykom podpowiadających (a czasem wręcz nakazujących), jak powinien wyglądać pisany przez nas kod. Dotyczy to na przykład instrukcji rzekomo złych “z natury” i innych tego rodzaju przesądów. Tak, uważam określenie ‘przesądy’ za uprawnione w wielu przypadkach.
Nie jestem ponadto odosobniony w tych poglądach – ba, wśród swoich kolegów po fachu mógłbym wskazać wielu kilku, którzy są pod tym względem nawet bardziej… radykalni :) Jak już jednak wspomniałem na początku, nie zawsze tak było. Kiedyś miałem bardziej rygorystyczne i pryncypialne podejście do kwestii tego, jak kodować należy lub nie należy. Skąd brałem dla niego uzasadnienie, jeśli nie z praktyki, której wtedy zbyt wiele jeszcze nie miałem?…
Ano właśnie – tu dochodzimy do sedna. Przekonanie to brało się głównie – jeśli nie wyłącznie – ze wszelkiego typu materiałów pomocniczych do nauki programowania: książek, kursów, artykułów, czasem dokumentacji. Choćby nie wiem jak techniczne i merytoryczne były te teksty, w każdym – co do jednego, z moim skromnym dziełem włącznie – znajdą się rady, zalecenia, wskazówki i inne “metainformacje” odnośnie tego, jak programować należy – a nie tylko o tym, jak można. Czy ktoś widział w kursie programowania opis instrukcji goto
(że pozwolę sobie użyć najbardziej typowego przykładu), który nie zawierał chociaż śladowej wzmianki o tym, iż.. no, w zasadzie to nie należy jej używać?… …Tak myślałem ;-)
Nasuwającą się od razu repliką w obronie autorów jest sugestia, że w sumie to przecież całkiem dobrze, iż uczą oni nowicjuszy tego, co sami musieli wcześniej wypracować przez lata. Nie wątpię wręcz, że taka intencja – oszczędzenia początkującym trudności – rzeczywiście autorom przyświeca. Dodają oni do pisanych przez siebie tekstów, kursów, tutoriali, itp. liczne wskazówki, te wszystkie dos and don’ts właśnie dlatego, iż wierzą w to, że w ten sposób pomogą swoim czytelnikom od razu, już na starcie kodować w sposób przejrzysty, efektywny, elastyczny, elegancki, “łatwo konserwowalny”, i tak dalej.
Chwalebne intencje – nie da się ukryć. Ale nietrudno jest wskazać przynajmniej dwa błędy takiego rozumowania. Pierwszym jest założenie, że każdą wiedzę i każdą umiejętność da się przekazać innym – czyli że w ogóle istnieje “droga na skróty”, niejako omijająca zdobywanie doświadczenia praktycznego w danej dziedzinie (w tym przypadku w programowaniu). Żeby stwierdzić, że to jednak nieprawda, wystarczy zastanowić się, czy ukończenie podstawowego kursu szachów u arcymistrza da nam od razu ELO 2500. Niestety w rzeczywistości dojście do takiego poziomu wymaga dziesiątków tysięcy rozegranych partii – i nic się na to nie poradzi.
Drugim błędem jest przyjmowanie, że przekazywane mądrości są faktycznie odpowiednie dla odbiorców. Nie, nie chcę wcale sugerować, że ktoś może mieć traumę z powodu wczesnych doświadczeń z hermetyzacją czy wzorcem fabryki ;) Raczej sugeruję, że przywiązując zbyt wielką wagę do przekazywanych zaleceń, początkujący mogą stracić wiele czasu na dopasowywanie swoich pierwszych programów do ich wysoko zawieszonej poprzeczki – najczęściej zupełnie niepotrzebnie. Gra w zgadywanie liczby, kółko i krzyżyk czy tetris nie potrzebują zwykle skomplikowanej, wewnętrznej architektury z dokładnie zaprojektowanymi relacjami między klasami i starannym oddzieleniem interfejsu od implementacji. Prosi się to bowiem o przytoczenie znanego powiedzenia na temat artylerii i pewnego gatunku insekta :)
Tak oto okazuje się więc, że zalecenia i rady tudzież – szumnie nazywane – standardy kodowania są “dziedziczone” przez początkujących programistów od autorów książek, kursów i tutoriali, którzy je tam umieszczają pomiędzy objaśnieniami kolejnych konstrukcji językowych czy elementów API. A że ponadto, jak opisałem wyżej, standardy te zbyt wcześnie poznane mogą być nie tyle nawet zbędne, co wręcz szkodliwe, pozwalam sobie nazwać je ‘chorobami dziedzicznymi’. Nic tak bowiem nie trafia do wyobraźni jak obrazowe porównanie ;>
Zakończyć chcę jednak optymistycznym akcentem. Otóż choroby te są jak najbardziej uleczalne. Terapia jest też nieskomplikowana i – jak sądzę – skuteczna. Trzeba po prostu kodować, kodować i jeszcze raz kodować :)
…czyli kilka kolejnych ciekawych rzeczy o lojbanie.
Trzecią notkę o lojbanie zacznę od przypomnienia części tego, o czym wspominałem w pierwszym wpisie. Mam tu na myśli postać “zdań” w lojbanie (bridi), składających się z predykatu (selbri) i określonej liczby argumentów (sumti). Przykładem może być:
mi sampla lo ceirdero
co bardzo dobrze pokazuje, że w lojbanie można mówić o wielu ciekawych rzeczach, jako że znaczenie tego bridi to… ‘Ja programuję shader‘ :) Użyty tu predykat sampla definiuje relację między programistą a pisanym przez niego programem/kodem i umożliwia również podanie trzeciego argumentu (docelowego przeznaczenia tego programu), który tutaj nie został wypełniony. Dwa pierwsze – mi i lo ceirdero – zostały jednak podane, wskakując, odpowiednio, na pierwsze i drugie miejsce predykatu sampla, zgodnie ze zdefiniowanym dla niego porządkiem argumentów, których nie da się dowolnie przestawiać bez zmiany znaczenia. Widać więc, że selbri w lojbanie przypominają funkcje z języków programowania ze wszystkimi parametrami zadeklarowanymi jako opcjonalne.
Ta analogia jest jednak tylko trochę adekwatna, bo bridi może mieć znacznie bardziej skomplikowaną strukturę niż najbardziej nawet zagnieżdżone wywołanie funkcji. I tak pierwszą ciekawostką jest to, że argumenty (czyli sumti) możemy przestawiać niemal wedle uznania, zachowując to samo znaczenie wypowiedzi. Mogę więc powiedzieć:
lo ceirdero cu se sampla mi
uzyskując coś w rodzaju strony biernej – ‘Shader jest pisany przeze mnie’. Niech ów shader ma w założeniu wykonywać najbardziej typową dla shaderów czynność – czyli wyświetlać imbryczek :) Wiedząc o tym, mogę wypełnić pominięte wcześniej trzecie miejsce sampla:
mi sampla lo ceirdero lonu jarco tu’a le tcati kabri
Mając trzy argumenty mogę z kolei poprzestawiać je w nieco bardziej zakręcony sposób – na przykład tak:
fi lonu jarco tu’a le tcati kabri cu sampla fa mi fe lo ceirdero
Fizycznie żaden argument nie jest tu na swoim miejscu, ale znaczenie pozostaje to samo. Taki zabieg, jak widać, wymagał dodania znaczników fa, fe oraz fi. Przypomina to trochę tzw. argumenty słownikowe w niektórych wysokopoziomowych językach programowania, takich jak Python.
Od znaczników FA niedaleko już z kolei do tzw. etykiet sumti (sumti tcita, zwanych też “tagami modalnymi” – modal tags). Jest to mechanizm umożliwiający dodanie do bridi argumentów, które nie są przewidziane w “deklaracji” selbri. Przykładowo, w naszym predykacie sampla wypełnione są już wszystkie miejsca, ale to nie znaczy wcale, że nie możemy do niego nic dodać. Skoro mówimy o kodowaniu, to naturalne jest chociażby określenie języka, w którym piszemy. Na szczęście odpowiednie miejsce możemy sobie dodać właśnie przy pomocy pewnego sumti tcita – mianowicie bau:
mi sampla lo ceirdero bau la’oi .HLSL.
Piszę shader w języku HLSL.
Możliwych etykiet jest oczywiście mnóstwo. Część z nich wykazuje podobieństwo do przyimków, inne przypominają frazy określające położenie w czasie lub przestrzeni (‘zanim’, ‘po tym jak’, ‘po prawej’, ‘na górze’, itp.), a jeszcze inne opisują bardziej złożone relacje (‘z powodu’, ‘w celu’, ‘z punktu widzenia’, ‘używając’, …), które w zwykłych językach rzadko da się wyrazić jednym słowem.
Ogólny wniosek z tego jest taki, że chociaż bridi w lojbanie i funkcje w językach programowania są ideowo do siebie zbliżone, to te pierwsze wykazują znacznie większą elastyczność. Nie powinno być to zresztą specjalnie zaskakujące – w końcu mówimy o języku naturalnym dla ludzi (głównie), a nie o kodzie dla kompilatorów :)
Uwaga ogólna: ze względu na oczywisty brak miejsca nie mogę wyjaśniać każdej konstrukcji gramatycznej użytej w przykładach. Jeśli chcemy koniecznie poznać znaczenie i funkcje jakiegoś nieznanego słówka, najlepiej wpisać je w słowniku takim jak vlasisku. W tej notce takimi nowymi i pewnie nieznanymi słowami są np. cu, nu, tu’a i la’oi.
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 switch
a ;)
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 if
em? 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:
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:
To by wyszło gdyby nie feler w postaci pierwszego if
a, 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)
:
Robi on dokładnie nic, ale jednocześnie sprawia, że następujące dalej, prawdziwe if
y mają dokładnie taką samą strukturę. Teraz już możemy je skrócić:
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…
XML kojarzy się raczej z technologiami webowymi i językami, które się z nimi wiążą – jak C#, Java, Ruby, itp. Nie zapominajmy jednak, że jest to także pewien format zapisu informacji. Ma on swoje wady (duży rozmiar), ale również zalety – jak np. możliwość edycji dowolnym edytorem tekstu – które mogą być pożyteczne w wielu zastosowaniach. Także wtedy, gdy piszemy w C++.
Oczywiście stworzenie własnego parsera XML nie jest bardzo trudne – mogę to powiedzieć z czystym sumieniem, gdyż samemu zdarzyło mi się takie dzieła (o różnym stopniu skomplikowania i funkcjonalności) popełnić w ilości co najmniej dwóch :) Nie jest to jednak zajęcia specjalnie kreatywne. Są też dla niego alternatywy i właśnie o jednej z nich – bibliotece dla C++ przeznaczonej do obsługi XML-a – chciałbym dzisiaj napisać.
Nazywa się ona TinyXML i jest to nazwa nadzwyczaj trafna. Jest to bowiem mały (ok. kilka tysięcy linii) i zgrabny kawałek kodu, który pozwala operować na dokumentach XML zarówno sensie odczytu, jak i zapisu. Rozprowadzany jest on na licencji ZLib, pozwalającej na używanie w produktach komercyjnych i niekomercyjnych. A ponieważ składa się na niego tylko kilka plików .cpp, łatwo korzystać z niego w projektach albo nawet dołączyć do własnego frameworka czy engine‘u.
A korzystać z TinyXML warto. Wśród istotnych cech tej biblioteki można wymienić to, że:
<!DOCTYPE>
(czyli z czymś, czego nikt normalny już nie używa ;-))Nie mniej ważne jest to, że biblioteka TinyXML jest prosta w obsłudze. Jeśli mieliśmy coś wspólnego z przetwarzaniem XML w innych językach programowania, nie powinna nam ona sprawić żadnego kłopotu. Nie ma w niej naturalnie żadnych zaawansowanych feature‘ów w rodzaju zapytań XPath, transformacji XSL czy walidacji w oparciu o XML Schema, bo nie do takich zastosowań została ona stworzona. Jeżeli jednak nie potrzebujemy takiej zaawansowanej funkcjonalności, a chcemy jedynie np. odczytywać i zapisywać XML-owe pliki konfiguracyjne, stworzyć klienta RSS czy wykonywać tysiące innych pożytecznych, a nieskomplikowanych operacji na znacznikowym formacie danych, to TinyXML jest całkiem dobrym wyborem.
No, chyba że bardzo lubimy samodzielne pisanie parserów ;)
W wielu językach wyjątki to niemal obowiązkowa metoda zgłaszania i obsługi błędów. W innych (tj. w C++) jest ona w dużym stopniu opcjonalna. W obu przypadkach istnieją zwykle dostępne out-of-the-box klasy wyjątków, których obiekty można wyrzucać, bez tworzenia swoich własnych. Ich używanie jest zdecydowanie wskazanie. Warto więc przyjrzeć się, jakie predefiniowane obiekty wyjątków przewidują standardowej biblioteki różnych języków.
I tak w C++ mamy bazową klasę exception
, która zawiera w sumie niewiele więcej ponad komunikat o błędzie. Zresztą dotyczy to tak naprawdę wszystkich wbudowanych w C++ klas wyjątków; tym, co je rozróżnia, jest po prosty typ. Mamy tu prostą hierarchię dziedziczenia, wyróżniającą klasy bazowe: runtime_error
dla niskopoziomowych błędów czasu wykonania (jak na przykład overflow_error
) oraz logic_error
dla błędów logicznych aplikacji. Z tej drugiej, bardziej dla nas interesującej, wyprowadzone są też wyjątki nieco bardziej szczegółowe, jak np.: invalid_argument
, range_error
, domain_error
czy lenght_error
. Wszystkie cztery są zresztą podobne do siebie pojęciowo i wydają się służyć głównie do sygnalizowania nieprawidłowych wartości argumentów funkcji. Do innego rodzaju błędów sensowniej jest używać klas bazowych lub niestety napisać własne (najlepiej dziedziczące z nich).
Na platformie .NET i w Javie jest z tym dużo lepiej – tam typów wyjątków jest niemal za dużo. Nie ma tu miejsca na omawianie ich wszystkich i nie jest to zresztą konieczne, gdyż nazwy klas są zwykle wystarczające do zorientowania się, z jakim rodzajem błędu mamy tu do czynienia. Według mnie najczęstszymi sytuacjami, w których można wyrzucić obiekt którejś z natywnych klas, są:
System.ArgumentException
/ java.lang.IllegalArgumentException
null
-em w sytuacji, gdy wymagany jest obiekt – System.ArgumentNullException
System.IndexOutOfRangeException
/ java.lang.IndexOutOfBoundsException
System.InvalidOperationException
/ java.lang.IllegalStateException
System.NotImplementedException
System.NotSupportedException
/ java.lang.UnsupportedOperationException
System.IO.IOException
/ java.io.IOException
System.TimeoutException
/ java.util.concurrent.TimeoutException
Można zauważyć, że dla większości pozycji odpowiadające klasy wyjątków istnieją na obu platformach i nawet nazywają się podobnie. Zapewne to kolejny znak wzajemnej inspiracji ich twórców ;)