Wspomnę dzisiaj o dość dziwnej funkcji, która została dodana w wersji 3.0 języka C#. Polega ona na możliwości dodania nowych metod do istniejących klas bez zmiany ich definicji. Odbywa się to poprzez zdefiniowanie tych dodatkowych metod jako statycznych (w innych klasach) i użyciu specjalnej składni dla jej pierwszego parametru. Oto przykład:
Jeśli ktoś co nieco wie o tym, jak od środka działają metody obiektów w C++, to pewnie przypomina sobie, że to co wewnątrz metody jest wskaźnikiem this
w rzeczywistości przekazywane jest metodzie jako jej pierwszy parametr. Pewnie ten fakt posłużył twórcom C# jako inspiracja przy ustalaniu składni metod rozszerzających.
Jak to działa? Otóż bardzo prosto. Dzięki powyższej definicji standardowa klasa System.String
została teraz wzbogacona o nową metodę WordCount
, której możemy użyć tak samo, jak każdej innej metody tej klasy:
Powód, dla którego fakt istnienia podobnego feature‘a nazwałem dość dziwnym, powinien się w tej chwili stać nieco jaśniejszy. Po krótkim zastanowieniu można bowiem stwierdzić, że niemal identyczny efekt można osiągnąć dwoma innymi sposobami:
StringUtils.WordCount(s)
. Jak widać, co najmniej jeden z tych sposobów można zastosować do każdej klasy – także takiej, której definicją nie dysponujemy (co w gruncie rzeczy jest banalne; na takich “sztuczkach” polega przecież całe programowanie obiektowe ;-]).Cechą wspólną obu tych “obejść” jest to, iż to my chcemy mieć tę nową metodę i to my ją definiujemy – zapewne dlatego, że to my chcemy jej potem używać. A skoro tak, to mamy wolność w ustaleniu sposobu jej wywoływania, ergo: nie ma znaczenia, jak ją zdefiniujemy. Któryś z tych dwóch/trzech sposobów będzie więc wystarczający.
Czyżby więc wychodziło na to, że C# w końcu dorobił się feature‘a, który rzeczywiście niczemu nie służy i jest kompletnie bez sensu? :) Prawie… Jak to z większością nowości w C# 3.0, przyczyna wprowadzenia metod rozszerzających streszcza się w – za przeproszeniem – czterech literach: LINQ.
LINQ opiera się bowiem na rozszerzeniu interfejsów pojemników (np. IEnumerable
) o dodatkowe metody służące wykonywaniu na nich quasi-SQL-owych zapytań – jak np. Select
czy Where
. One właśnie zostały zaimplementowane jako metody rozszerzające. A że ponadto trafiły one do przestrzeni nazw System.Linq
, to stają się dostępne dopiero po zadeklarowaniu użycia tej właśnie przestrzeni.
Reasumując: twórcy .NET-a dodali dwie nowości (metody rozszerzające oraz LINQ) po to, żeby ta pierwsza pozwalała na nieużywanie tej drugiej. Genialne, czyż nie? ;D
Jeśli staramy się pisać kod zgodnie z dobrymi praktykami programowania w C/C++, to niezbyt często korzystamy z dyrektywy #define
. W większości przypadków ogranicza się to zresztą do zdefiniowania jakiegoś symbolu służącego do kompilacji warunkowej (np. WIN32_LEAN_AND_MEAN
). Od czasu do czasu bywa jednak tak, że chcemy użyć #define
w jego – teoretycznie – głównym zastosowaniu, czyli do zastępowania jednego kawałka kodu innym.
Uzasadnione przypadki takiego postępowania (do których nie należą np. makra udające funkcje, czyli sławetne #define min
/max
) to tylko takie sytuacje, w których bez #define
rzeczywiście musielibyśmy wiele razy pisać (prawie) to samo. Moim ulubionym przykładem czegoś takiego jest wychodzenie z funkcji w wielu miejscach, gdzie przed każdym return
em musimy coś jeszcze zrobić – np.:
Zwyczajne ustawienie *wynik = 0;
na początku funkcji będzie nieakceptowalne, jeśli chcemy, by w przypadku rzucenia wyjątku w którymś z /*... */
podana do funkcji zmienna pozostała bez zmian.
Prostym rozwiązaniem wydaje się więc makro:
definiowane przed i #undef
owane tuż po funkcji. Pomysł byłby dobry, gdyby nie to, że w tej postaci makro to powodowałoby błędy składniowe. Kiedy? Chociażby w takim if
ie:
Tutaj bowiem kompilator nie znalazłby instrukcji if
pasującej do else
, co jest naturalnie błędem. Powodem tego jest średnik po wystąpieniu makra, generujący pustą instrukcję i sygnalizujący (przedwczesny) koniec klauzuli if
.
Jak ominąć ten problem? Odpowiedź jest może zaskakująca: instrukcje trzeba zamknąć w… pętlę:
Jak nietrudno zauważyć, taka pętla wykona się dokładnie raz (i co więcej, kompilator o tym doskonale wie, zatem może ją zoptymalizować – tj. wyrzucić wszystko poza zawartością). Działanie jest więc takie same. Różnica polega na tym, że teraz takie makro może być używane w dowolnym miejscu bez żadnych niespodziewanych efektów.
Przez ostatni tydzień byłem na małych wakacjach, więc nie zajmowałem się niczym na tyle pożytecznym, by dało się o tym tutaj napisać ;] Ale na szczęście w międzyczasie zdarzyło się coś, o czym zdecydowanie warto wspomnieć.
Wczoraj mianowicie potwierdziły się spekulacje odnośnie kolejnego, trzeciego już dodatku do gry World of Warcraft. Rzecz była w sumie do przewidzenia, bowiem – podobnie jak dwa poprzednie – i to rozszerzenie zostało oficjalnie ogłoszone podczas BlizzConu. Co tam więc nowego ma zostać dodane do świata Azeroth?…
Ano odpowiedź brzmi: prawie nic albo niemal wszystko. W przeciwieństwie do poprzednich dodatków, ten nie doda żadnego nowego, dużego kontynentu do zwiedzania przez wysokopoziomowych graczy, a jedynie kilka nowych, mniejszych lokacji rozsianych po świecie. Tenże świat ma być jednak gruntownie przekształcony w wyniku tajemniczej i niezwykle brzemiennej w skutkach katastrofy – kataklizmu. Taki jest zresztą tytuł rozszerzenia – World of Warcraft: Cataclysm. Sprawcą całego zamieszania ma tu być Deathwing – pewien niezbyt przyjemny czarny smok, którego niektórzy może pamiętają z Warcrafta 2.
Jak widać, fabularnie prezentuje się to tak sobie, ale ciężko oczekiwać po tylu latach jakiejś wielkiej kreatywności :) To co bardziej interesujące, to oczywiście zmiany w samej grze, które dodatek ma przynieść. A są one następujące:
Część zmian wydaje się drobna, gdyż tak naprawdę są fragmentem dość długiej listy niewielkich, acz ciekawych modyfikacji, które dodatek ma przynieść; wybrałem te, które wydały mi się najważniejsze. Widać oczywiście, że są też i większe nowości, ale nietrudno zauważyć, że brak wśród nich jednej z bardziej oczekiwanych – nowej klasy. Jej w Kataklizmie nie uświadczymy.
Tak w dużym skrócie całość się przedstawia i trudno byłoby zaprzeczyć, że wygląda to całkiem ciekawie. Osobiście wydaje mi się, że to właśnie rozszerzenie może być decydujące dla dalszych losów gry. Jeśli okaże się, że lifting starych, liczących sobie już prawie 5 lat krain wypadł pomyślnie, może to przyciągnąć jeszcze większą liczbę graczy i zapewnić WoW-owi pozycję lidera na kolejne kilka lat – czyli mniej więcej na tyle, na ile rozciągają się prawdopodobne plany kolejnych 2-3 dodatków. (Tę liczbę można wywnioskować z tempa podnoszenia maksymalnego poziomu postaci i niedwuznacznych sugestii Blizzarda, że poziom 100 będzie ostatnim). Nie muszę pewnie dodawać, że szansa na to, iż będzie inaczej Cataclysm okaże się komercyjną klapą, są – znając Blizzarda – baaardzo małe :)
Jak będzie w rzeczywistości, zobaczymy pewnie gdzieś w okolicach końcówki przyszłego roku. Ja mam tylko nadzieję, że Starcraft 2 będzie wydany wcześniej ;D
Programy działające w tle (np. komunikatory) często używają różnego rodzaju “wyskakujących” powiadomień, które w założeniu mają pojawiać się tuż obok zasobnika systemowego (system tray), gdzie wspomniane programy mają swoje ikonki. W rzeczywistości pozycja tych komunikatów jest często hardcode‘owana w okolicach prawego dolnego rogu ekranu. Jeśli więc użytkownik przypadkiem ma pasek zadań gdzie indziej, to wtedy cały misterny plan idzie w… las ;]
Dlatego lepszym rozwiązaniem jest sprawdzanie, gdzie tak naprawdę ów pasek się znajduje. Przez długi czas można było to zrobić poprzez zwyczajne pobranie jego uchwytu (HWND
) i odczytanie wymiarów tak, jak każdego innego okna – np. przez GetWindowRect
:
Począwszy od Windows Vista nie jest to jednak możliwe, gdyż ze względu na bezpieczeństwo (tzw. izolacja sesji 0) nie da się już tak po prostu pobrać uchwytu do paska zadań ze zwykłej aplikacji. Istnieje na szczęście przenośny sposób na pobranie jego pozycji; w tym celu trzeba się posłużyć funkcją powłoki systemowej SHAppBarMessage
(z nagłówka shellapi.h i biblioteki shellapi.lib):
Tak możemy otrzymać nie tylko wymiary prostokąta okalającego pasek, ale też informację o tym, przy której krawędzi się on znajduje. Jest ona zawarta w polu uEdge
struktury zwracanej przez funkcję SHAppBarMessage
i równa którejś ze stałych ABE_BOTTOM
, ABE_TOP
, itd.
Mając te dwie informacje możemy nie tylko wyświetlać nasze powiadomienia w odpowiednim miejscu, ale też zapewnić, żeby w razie ich nagromadzenia “rosły” one w odpowiednim kierunku.
Kiedy w klasie pochodnej piszemy nową wersję funkcji wirtualnej, to w C++ powinniśmy zawsze uważać na to, żeby jej nagłówek był identyczny z tym, który zdeklarowano w klasie bazowej. To dlatego, że składnia języka nie wymaga w tej sytuacji żadnego słowa kluczowego w rodzaju override
. Jeśli więc coś nieopatrznie zmienimy, to nie uzyskamy nowej wersji funkcji, tylko zupełnie nową funkcję przeciążoną:
Taki efekt jest jest w gruncie rzeczy oczywisty i łatwy do zrozumienia. Pewnie więc będzie nieco zaskakujące dowiedzieć się, że tak naprawdę postać funkcji wirtualnej w klasie pochodnej może się trochę różnić, a mimo to wciąż będziemy mówili o tej samej funkcji.
Jak to jest możliwe?… Dopuszcza się mianowicie różnicę w typie zwracanym przez nową wersję funkcji wirtualnej. Może on być inny od typu zwracanego przez wersję z klasy bazowej – ale tylko pod warunkiem, że:
int
)const
/volatile
co typ pierwotnyPrzykładowo więc, jeśli w klasie bazowej funkcja wirtualna zwraca const A*
, to w klasie pochodnej może zwracać const B*
, o ile klasa B
dziedziczy publicznie po A
. Nie może za to zwracać samego B*
(niestałego) lub const X*
, gdy klasa X
jest niezwiązana z A
.
Do czego taki “myk” może się przydać? Najczęściej chodzi tutaj o sytuacje, gdy mamy do czynienia z równoległym dziedziczeniem kilku klas, które na każdym poziomie są związane ze sobą. Mogę sobie na przykład wyobrazić ogólny menedżer zasobów w grze, którego metoda wirtualna Get
zwraca wskaźnik na bazową klasę Resource
, a następnie bardziej wyspecjalizowany TextureManager
, który w tej sytuacji podaje nam wskaźnik na Texture
. (Oczywiście klasa Texture
w założeniu dziedziczy tu po Resource
). Czasami coś takiego może być potrzebne, aczkolwiek takie równoległe hierarchie dziedziczenia nie są specjalnie eleganckie ani odporne na błędy.
Lepszym przykładem jest wirtualny konstruktor kopiujący: metoda, która pozwala na wykonanie kopii obiektu bez dokładnej znajomości jego typu. Zwyczajowo nazywa się ją Clone
:
Dzięki temu że metoda jest wirtualna, można ją wywołać nie znając rzeczywistego typu obiektu (co nie jest możliwe w przypadku zwykłych konstruktorów, które w C++ wirtualne być nie mogą). W wyniku jej wywołania otrzymamy jednak tylko ogólny wskaźnik Object*
na powstałą kopię obiektu.
Gdybyśmy teraz nie zmienili typu zwracanego przez metodę w klasie pochodnej, to klonowanie podobne do tego:
wymagałoby dodatkowego rzutowania w górę, by mogło zostać przepuszczone przez kompilator.
Chcąc w prosty sposób odtworzyć dźwięk w programie pod Windows, możemy skorzystać z funkcji WinAPI o nazwie PlaySound
. Wystarczy podać jej nazwę pliku dźwiękowego, by ten został odegrany:
Jak to jednak bywa w przypadku metod najprostszych, nie jest to rozwiązanie doskonałe. Jedną z jego wad jest bowiem to, że próbka dźwiękowa jest tutaj wczytywana z dysku tuż przed jej pierwszym odtworzeniem. W większości przypadków trwa to chwilę, którą da się zauważyć jako krótkie opóźnienie przed usłyszeniem dźwięku.
W grach (i nie tylko) jest to oczywiście niedopuszczalne, więc zwykle najlepszym wyjściem jest użycie wyspecjalizowanych bibliotek dźwiękowych. Jeśli jednak nie chcemy dołączać dodatkowych plików DLL lub wykorzystywać DirectX-a, możemy temu opóźnieniu zaradzić.
Funkcja PlaySound
pozwala bowiem na odtworzenie dźwięku nie tylko z pliku, ale też z pamięci operacyjnej:
W tym celu posłużyć się trzeba flagą SND_MEMORY
i podać wskaźnik do bloku pamięci, w którym znajduje się nasza próbką dźwiękowa. Skąd go wziąć? Ano chociażby wczytać plik dźwiękowy z dysku do pamięci:
“Trik” polega na tym, że operację wczytywania możemy przeprowadzić wcześniej, a więc w trakcie ładowania całej gry, pojedynczego etapu, pokoju, krainy, itp. – czyli wtedy, gdy jej potencjalnie długi czas nie będzie nikomu przeszkadzał. Potem zaś da się ją odtworzyć z pamięci i nie doświadczać żadnych opóźnień.
Trzeba tylko pamiętać, żeby na koniec zwolnić pamięć przeznaczoną na wczytaną próbkę.
Prawie każdy potrafi sklecić prostą stronę WWW, nie mówiąc już o napisaniu nieskomplikowanego tekstu w formacie HTML. Do tego celu wystarczy zwykle użyć od kilku do kilkunastu standardowych tagów, odpowiedzialnych za podstawowe formatowanie (pogrubienie, kursywa, itp.) oraz wstawianie obiektów w rodzaju obrazków czy tabel.
W rzeczywistości istnieje jednak znacznie więcej znaczników, z których wiele jest bardzo mało znanych. Oto kilka spośród nich:
<abbr>
i <acronym>
służą do wstawiania akronimów (odpowiednio: literowych i głoskowych) wraz z rozwinięciem (podawanym przy pomocy atrybutu title
). Przykładami mogą być chociażby NATO czy PZU. W większości przeglądarek owo rozwinięcie pojawi się po najechaniu kursorem na dany akronim.<dl>
pozwala na stworzenie tzw. listy definicyjnej, czyli krótkiego słownika pojęć. Każdy termin należy w niej umieścić wewnątrz tagu <dt>
, zaś jego rozwinięcie w tagu <dd>
. W wyniku możemy otrzymać coś takiego:
<em>
, <code>
, <kbd>
oraz kilka podobnych to próba dodania do HTML sposobu na określenie znaczenia (a nie tylko formatowania) poszczególnych fragmentów dokumentu. W jaki sposób te tagi są interpretowane przez wyszukiwarki pozostaje raczej niejasne, ale można podejrzewać, że większość nie przywiązuje do nich dużej wagi, bo ich używanie na stronach nie jest specjalnie częste. Jeśli jednak zdecydujemy się na konsekwentne korzystanie z nich, to zaletą takiego postępowania będzie łatwość sformatowania np. wszystkich kawałków kodu na naszej stronie przy pomocy odpowiedniego stylu zaaplikowanego do znaczników <code>
. Trudno aczkolwiek nie zauważyć, że klasy w CSS działają właściwie tak samo…<ins>
i <del>
mogą służyć do oznaczania zmian w dokumencie: tekstu wstawionego lub usuniętego, łącznie z możliwością podania daty dokonania zmiany. Żadna z głównych przeglądarek nie obsługuje jednak tego feature‘u, więc póki co przydatność tych tagów jest bliska zeru.Oczywiście lista ta jest daleka od bycia kompletną, bo zasób wszystkich znaczników HTML liczy sobie dobrych kilkadziesiąt pozycji. Warto go przejrzeć, bo istnieje duża szansa, że natrafimy na coś przydatnego :)