Posts tagged ‘C/C++’

Typ std::bitset i flagi logiczne

2010-05-27 15:52

Napiszę dzisiaj o nierzadko zapominanej klasie z biblioteki standardowej C++, czyli o tytułowym zbiorze bitówstd::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:

  1. dev->Clear (0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
  2.     D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0);

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 enuma, np.:

  1. enum FontStyle { Bold, Italic, Underline, FontStylesCount };

bitset jest wtedy łatwy w użyciu:

  1. void FormatText(const std::string& text,
  2.     const std::bitset<FontStylesCount>& style)
  3. {
  4.     if (style.any())  // czy jakikolwiek bit został ustawiony
  5.     {
  6.         // sprawdzenie poszczególnych flag
  7.         if (style[Bold]) { /* ... */ }
  8.         if (style[Italic]) { /* ... */ }
  9.         if (style[Underline]) { /* ... */ }
  10.     }
  11. }

Zachowana jest przy tym “kompatybilność wsteczna” w stosunku do ręcznie definiowanych stałych, jeśli chcielibyśmy z nich korzystać:

  1. #define OPT(x) (1<<(x))
  2. FormatText ("Hello World!", OPT(Bold) | OPT(Italic));&#91;/cpp]
  3. lub jeżeli jakieś zewnętrzne API, w którym programujemy, korzysta z wartości bitowych zapisywanych w typach liczbowych:
  4. &#91;cpp]std::bitset<32> lp = lParam;
  5. lp.set(30).reset(31);
  6. SendMessage (hWnd, WM_KEYDOWN, VK_UP, lp.to_ulong());

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 :)

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

if (false) { }

2010-05-22 16:49

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 switcha ;)
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 ifem? 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:

  1. if (s == "foo") DoSth1(z);
  2. else if (s == "bar") DoSth2(x, y);
  3. else if (s == "baz") DoSth3();

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:

  1. #define C(val,code) else if (s == #val) { code; }

To by wyszło gdyby nie feler w postaci pierwszego ifa, 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):

  1. if (false) { }
  2. else if (s == "foo") DoSomething1(z);
  3. // ...

Robi on dokładnie nic, ale jednocześnie sprawia, że następujące dalej, prawdziwe ify mają dokładnie taką samą strukturę. Teraz już możemy je skrócić:

  1. #define C(val,code) else if (s==#val) {code;}
  2. if (false) { } C(foo,DoSth1(z)) C(bar,DoSth2(x,y)) C(baz,DoSth3())
  3. #undef C

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…

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

XML w C++ – proste rozwiązanie

2010-05-19 16:13

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:

  • parsuje ogromną większość dokumentów XML; aktualnie kłopoty ma jedynie z tymi, w których specyfikacje DTD są osadzone bezpośrednio w sekcji <!DOCTYPE> (czyli z czymś, czego nikt normalny już nie używa ;-))
  • opiera się na modelu DOM (Document Object Model), czyli udostępnia zawartość plików XML w postaci drzewka elementów
  • obsługuje kodowanie UTF-8
  • używa STL i operatorów strumieniowych – ale tylko wtedy, jeśli o to poprosimy, definiując odpowiednie makro w czasie kompilacji

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 ;)

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

Co się rzuca, czyli natywne klasy wyjątków

2010-05-16 13:46

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ą:

  • ogólnie pojęte niepoprawne wartości argumentów funkcji – System.ArgumentException / java.lang.IllegalArgumentException
  • argument będący null-em w sytuacji, gdy wymagany jest obiekt – System.ArgumentNullException
  • wykroczenie poza zakres argumentu działającego jak indeks – System.IndexOutOfRangeException / java.lang.IndexOutOfBoundsException
  • próba wykonania operacji na obiekcie, którego stan jest niepoprawny – System.InvalidOperationException / java.lang.IllegalStateException
  • wywołanie niezaimplementowanej metody wirtualnej – System.NotImplementedException
  • próba wykonania operacji, której obiekt nie wspiera – System.NotSupportedException / java.lang.UnsupportedOperationException
  • błąd podczas operacji wejścia/wyjścia – System.IO.IOException / java.io.IOException
  • przekroczenie czasu operacji (timeout) – 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 ;)

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

Po co jest FORCE_DWORD

2010-05-12 15:28

Przeglądając dokumentację do DirectX (a przynajmniej do części graficznej) można natknąć się na wiele typów wyliczeniowych. Większość z nich (a może wszystkie?) na końcu swojej definicji ma stałą o nazwie kończącej się na _FORCE_DWORD. Przykładem jest znany, lubiany i przez wszystkich używany D3DRENDERSTATETYPE:
typedef enum D3DRENDERSTATETYPE
{
// (co najmniej 1<<8 różnych stałych) D3DRS_FORCE_WORD = 0x7fffffff; } D3DRENDERSTATETYPE, *LPD3DRENDERSTATETYPE;[/cpp] Zjechanie na sam dół pomocy pouczy nas, że stała ta zasadniczo... nie jest używana. Jednocześnie jednak ma ona wymuszać kompilację typu wyliczeniowego jako 32-bitowego. O co tutaj właściwie chodzi? Kompilatory C++ mogą mianowicie wybierać dla typów wyliczeniowych właściwie dowolne typy liczbowe - byle tylko wszystkie wartości stałych się zmieściły. To sprawia, że wielkość enuma może się różnić nie tylko między kompilatorami, ale i między różnymi ustawieniami kompilacji. Nietrudno na przykład wyobrazić sobie, że przy optymalizacji szybkości ów enum będzie miał rozmiar równy słowu maszynowemu, zaś przy optymalizacji zajętości pamięci będzie to rozmiar najmniejszy możliwy.
No i tu zaczynają się schody tudzież pagórki. Zmienność (a raczej niezdefiniowanie) wielkości typu wyliczeniowego jest może kłopotliwa w pewnych sytuacjach. Trudno byłoby na przykład przewidzieć to, w jaki sposób należy odczytać wartość zwróconą przez metodę GetRenderState urządzenia, która jest zapisana w 32-bitowym DWORD-zie, jeśli nie zajmowałaby ona w nim wszystkich 4 bajtów. Podejrzewam też, że na którymś etapie renderowania we wnętrzu DirectX określony rozmiar pewnych flag (np. typów prymitywów) jest po prostu wymuszany przez sterownik karty graficznej. Całkiem rozsądne jest więc zapewnienie go od samego początku – czyli już w kodzie pisanym przez programistę-użytkownika DirectX.

Czemu jednak potrzebny jest takich hack? Ano tutaj znowu wychodzi niedookreślenie pewnych rzeczy w standardzie C++, zapewne z powodu źle pojętej przenośności. Częściowo zostanie to naprawione w przyszłej wersji standardu, gdzie – podobnie jak np. w C# – możliwe będzie określenie typów liczbowych używanych wewnętrznie przez enumy.

Tags: , , , ,
Author: Xion, posted under Programming » Comments Off on Po co jest FORCE_DWORD

Co może udawać obiekt w C++

2010-05-06 14:56

Możliwości przeciążania operatorów w C++ dla własnych typów obiektów sprawiają, że mogą one (tj. te obiekty) zachowywać się w bardzo różny sposób. Mogą na przykład “udawać” pewne wbudowane konstrukcje językowe, nierzadko wykonując ich zadania lepiej i wygodniej. Przykładów na to można podać co najmniej kilka – oto one:

  • Obiekt może zachowywać się jak funkcja, czyli udostępniać możliwość “wywołania” siebie z określonymi parametrami. Takie twory często nazywa się funktorami i bywają używane podczas pracy ze standardową biblioteką STL.
    Działają one przy tym w bardzo prosty sposób, zwyczajnie przeciążając operator nawiasów okrągłych (). Jest on na tyle elastyczny, że może przyjmować dowolne parametry i zwracać dowolne rezultaty, co pozwala nawet na stworzenie więcej niż jednego sposobu “wywoływania” danego obiektu. Jednym z bardziej interesujących zastosowań dla tego operatora jest implementacja w C++ brakującego mechanizmu delegatów, czyli wskaźników na metody obiektów.
  • Na podobnej zasadzie obiekt może udawać tablicę – wie o tym każdy, kto choć raz używał klas STL typu vector. Wymagane jest tu przeciążenie operatora indeksowania []. Daje ono wtedy dostęp do elementów obiektu-pojemnika, który zresztą nie musi być wcale indeksowany liczbami, jak w przypadku tablic wbudowanych (dowodem jest choćby kontener map). Ograniczeniem jest jedynie to, że indeks może być (naraz) tylko jeden, bo chociaż konstrukcja typu:
    1. v[3,4] = 5;

    jest składniowo najzupełniej poprawna, to działa zupełnie inaczej niż można by było się spodziewać :)

  • Obiekty mogą też przypominać wskaźniki, które wtedy określa się mianem ‘sprytnych’ (smart pointers). Dzieje się tak głównie za sprawą przeciążenia operatorów * (w wersji jednoargumentowej) i ->. Normalnie te operatory nie mają zastosowania wobec obiektów, ale można nadać im znaczenie. Wtedy też mamy obiekt, który zachowuje się jak wskaźnik, czego przykładem jest choćby auto_ptr z STL-a czy shared_ptr z Boosta.
  • Wreszcie, obiekt może też działać jako flaga boolowska i być używany jako część warunków ifów i pętli. Sprytne wskaźniki zwykle to robią, a innymi wartymi wspomnienia obiektami, które też takie zachowanie wykazują, są strumienie z biblioteki standardowej. Jest to spowodowane przeciążeniem operatora logicznej negacji ! oraz konwersji na bool lub void*.

Reasumując, w C++ dzięki przeciążaniu operatorów możemy nie tylko implementować takie “oczywiste” typy danych jak struktury matematyczne (wektory, macierze, itp.), ale też tworzyć własne, nowe (i lepsze) wersje konstrukcji obecnych w języku. Szkoda tylko, że często jest to wręcz niezbędne, by w sensowny sposób w nim programować ;)

Argumenty w linii komend

2010-05-03 13:29

Parametry przekazywane programom podczas uruchomienia mają najczęściej formę kilku przełączników (switches), po których opcjonalnie występują właściwe dla nich argumenty. Zwyczajowo rzeczone przełączniki są poprzedzone myślnikiem (styl uniksowy) lub slashem (styl MS-DOS), jak w przykładzie poniżej:

gcc -c main.c -o main.o -pedantic

Tak skonstruowany wiersz poleceń jest przekazywany przez runtime do aplikacji C/C++ jako tablica ciągów znaków, powstałych już po podzieleniu go na części według spacji i ew. cudzysłowów. Tradycyjnie pierwszy parametr (argc) funkcji main określa liczbę tych części, zaś drugi (argv) jest wspomnianą tablicą je zawierającą. Warto pamiętać, że – jeśli interesują nas tylko argumenty wywołania – jest to tablica indeksowana od jedynki, gdyż argv[0] zawiera zawsze samo polecenie uruchamiające program (w powyższym przykładzie byłby to ciąg "gcc"). Ostatnim elementem jest z kolei argv[argc - 1], zatem w sumie tablica zawiera argc - 1 znaczących parametrów programu. Wreszcie, gwarantowane jest, iż argv[argc] jest zawsze pustym wskaźnikiem, jeśli do czegokolwiek mogłoby się to przydać.

Bezpośrednia interpretacja wiersza poleceń, który używa wspomnianych na początku przełączników, może być jednak nieco kłopotliwa. Wydaje mi się, że lepiej jest przetworzyć ją do bardziej znośnej postaci słownika (mapy), pozwalającego odwoływać się do switchy i ich argumentów po nazwie. Można to zrobić choćby w poniższy sposób:
#include
#include
#include
typedef std::map > CommandLine;

const char SWITCH_CHAR = ‘-‘; // lub ‘/’
const CommandLine ParseCommandLine(int argc, const char* argv[])
{
CommandLine cl;
for (int i = 1; i < argc; ) if (*(argv[i]) == SWITCH_CHAR) { std::vector p;
p.reserve (argc – i);

int j;
for (j = i + 1;
j < argc && (*(argv[j]) != SWITCH_CHAR || strstr(argv[j], " ")); ++j) p.push_back (argv[j]); cl.insert (std::make_pair(argv[i] + 1, p)); i = j; } else ++i; return cl; }[/cpp] W rezultacie otrzymamy mapę, w której wyszukiwanie (std::map::find) pozwoli nam określić, czy dany przełącznik był podany, zaś indeksowanie (operator []) da nam dostęp do jego parametrów w postaci wektora.

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


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