xion.log » 2010 » May

Monthly archive for May, 2010

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

Zamiast szumu wiatraczka

2010-05-07 15:53

Dzisiaj chcę napisać o ważnej kwestii okołokoderskiej, przed którą chyba każdy prędzej czy później stanął. Pisząc kod, nie zawsze potrzebujemy wielkiego skupienia i absolutnej ciszy; przeciwnie, wiele osób lubi programować przy dźwiękach muzyki. Zazwyczaj jest to jakaś własna kolekcja utworów, zorganizowana w playlisty.
Ma to swoje wady. Jedną z nich jest to, że wymaga sporo manualnej obsługi; jeśli jej zabraknie, to zawartość list(y) może nam się znudzić nawet w przypadku losowej kolejności odtwarzania (no, chyba że nasza kolekcja jest wystarczająco gigantyczna ;P). Poza tym w ten sposób nie usłyszymy nigdy niczego nowego, a być może wartego usłyszenia.
Z drugiej strony włączenie radia zwykle nie jest sensowną opcją – nawet jeśli chodzi o radia internetowe. Nie da się bowiem znaleźć takiego, które idealnie w wpasuje się w nasz muzyczny gust. A gdyby tak można było stworzyć własne?… Eh, pomarzyć dobra rzecz :)

Okazuje się jednak, że to zupełnie wykonalne. Istnieją serwisy, które właśnie na coś takiego pozwalają. Jednym z najstarszych i najbardziej znanych jest Last.fm, zawierający chyba wszystkie feature‘y, jakich można oczekiwać (łącznie ze stacjonarnym klientem), Niestety, jest on płatny.
Osobiście polecam darmowe Jango. Potrafi on dokładnie tyle, ile potrzeba – tj. tworzy wirtualne stacje radiowe (w dowolnej ilości, o ile mi wiadomo) grające muzykę wybranych wykonawców (z możliwością określenia preferencji do pojedynczych utworów) oraz artystów zbliżonych gatunkowo. To wyszukiwanie podobieństw działa przy tym całkiem dobrze, więc mamy spore szanse na odkrycie ciekawych, nieznanych wcześniej wykonawców. Wady?… Jak to bywa w przypadku takich serwisów, może to być niewystarczająco bogata biblioteka zarówno utworów, jak i artystów. Brakuje też desktopowego klienta, co może niektórym przeszkadzać; można wówczas spróbować zewnętrznego programu Jango Desktop.

Tags: , ,
Author: Xion, posted under Culture, Internet, Programming » 10 comments

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

Naprawianie strumienia wejścia

2010-05-01 12:46

Kiedy czytamy dane przy pomocy strumienia wejściowego w C++ (basic_istream), wszystko działa pięknie do momentu, gdy zgadzają się one z tym, czego oczekujemy. Ale w rzeczywistych, a nie hello-worldowych programach nie możemy oczekiwać, że np. poniższy kod:

  1. int n;
  2. cin >> n;

w jakiś magiczny sposób zmusi użytkownika, by wpisał liczbę. To samo dotyczy odczytu z plików. Program musi więc być odporny na nieprawidłowe dane.

Łatwo jest na szczęście ocenić, czy takie dane otrzymaliśmy – wystarczy sprawdzić flagi bitowe strumienia, co w najprostszej wersji wygląda po prostu tak:

  1. if (cin) { /* ok */ }

Równie łatwo jest przywrócić je do stanu używalności (metodą clear) i tym samym dać ponownie możliwość odczytu ze strumienia. Wtedy jednak okaże się, że tym, co chcemy odczytać, nie są żadne nowe dane, lecz dokładnie te same, które spowodowały oryginalny błąd.

Ma to sporo sensu – dzięki takiemu zachowaniu może podjąć próbę ich reinterpretacji jako innego typu danych. Zależy to oczywiście od logiki i struktury wejścia, które czytamy. Jeśli jednak rzeczone dane już nas nie interesują i chcielibyśmy raczej powtórzyć próbę odczytania tego samego, musimy się ich jakoś pozbyć.
Da się to zrobić całkiem prosto. Każdy strumień wejścia utrzymuje bowiem bufor odczytu (read buffer), do którego najpierw trafiają znaki z wejścia. Jeżeli okaże się, że ich format nie zgadza się z tym żądanym przez polecenie odczytu, to ów bufor nie jest opróżniany – stąd wynika opisane wyżej zachowanie strumienia. Żeby więc zacząć znowu czytać bezpośrednio z wejścia, bufor ten należy opróżnić. Mamy na szczęście do niego dostęp (metoda rdbuf zwraca na niego wskaźnik), zatem da się to zrobić – w nieco oldschoolowy sposób:

  1. template <typename T> void FixStream(basic_istream<T>& is)
  2. {
  3.     if (!is)
  4.     {
  5.         const int BUF_SIZE = 32;
  6.         T buf[BUF_SIZE];
  7.  
  8.         is.clear();
  9.         basic_streambuf<T>* isBuf = is.rdbuf();
  10.         int toRead = isBuf->in_avail();
  11.         while (toRead > 0)
  12.         {
  13.             int c = toRead > BUF_SIZE ? BUF_SIZE : toRead;
  14.             isBuf->sgetn(buf, c);
  15.             toRead -= c;
  16.         }
  17.     }
  18. }

W skrócie: czytamy z niego po kawałku znaki, aż w końcu nie będzie już niczego… do odczytania :) Pusty bufor sprawia wtedy, że kolejne operacje odczytu ze strumienia będą pobierały dane już bezpośrednio z wejścia.

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


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