Przecinek to taki niepozorny znak, którego grubo ponad 90% zastosowań ogranicza się zapewne do oddzielania parametrów funkcji (czy to w deklaracji, czy to w wywołaniu). Niektóre języki używają też go do separowania indeksów wielowymiarowych tablic, ale do nich C++ akurat nie należy.
Zamiast tego przecinek jest tam operatorem, o czym rzadko się pamięta. Być może dlatego, że ma on najniższy priorytet spośród wszystkich operatorów. Wydaje się też, że domyślne jego działanie (obliczenie wszystkich argumentów, a następnie zwrócenie ostatniego) nie jest specjalnie przydatne – jeśli w ogóle.
Można je jednak zmienić. Tak, tak – operator przecinka można przeciążać, jakkolwiek zaskakujące i przekombinowane by się to wydawało. W rzeczywistości jest on bowiem zwyczajnym operatorem binarnym o łączności lewostronnej i wspomnianym najniższym priorytecie. A przeciążyć można go, pisząc funkcję operator,()
– dokładnie tak samo, jak każdego innego dwuargumentowego operatora.
Do czego może to służyć? Otóż względnie typową sztuczką jest użycie przedefiniowanego przecinka do konstrukcji obiektów złożonych z wielu elementów, np. wektorów lub macierzy. Oto przykład przeciążenia dla standardowej klasy vector
:
Pozwala to na inicjalizację wektora w taki oto nietypowy sposób:
Pierwszy element tego ciągu (pusty wektor) jest konieczny, gdyż w przeciwnym razie użyty zostałby standardowy operator przecinka.
Na pierwszy rzut może to wyglądać efektownie. Pamiętajmy jednak, że przecinek ten pozostaje wciąż przecinkiem, zachowując chociażby swoją główną cechę szczególną, czyli priorytet. W szczególności próba dodania do wektora kolejnych elementów w ten sposób:
zakończy się niezupełnie po naszej myśli…
Mimo to być może warto spróbować wymyślić dla tego operatora jakieś własne, w miarę sensowne zastosowanie. Możliwe, że okaże się on wcale nie tak nieużyteczny, na jakiego zdaje się wyglądać :]
Kiedy zaczynamy uczyć się programowania, dowiadujemy się, że zmiennym przypisane są zawsze konkretne typy, których należy się trzymać. Trochę później, gdy zajmujemy się już shaderami i programowalnym potokiem graficznym, okazuje się, że większość wartości jest tam tego samego typu (wektor trzech lub czterech float
ów). Różnią się one jednak przypisaną im semantyką, czyli znaczeniem w opisie wierzchołka lub piksela (POSITION
, NORMAL
, COLOR
, i tak dalej).
Jednak sama semantyka nie jest jedyną cechą charakterystyczną danego wektora. Nawet mając do czynienia z dwoma wektorami “geometrycznymi” (czyli opisującymi pozycję lub normalną, a nie np. kolor), nie zawsze możemy wykonywać na nich łączne operacje. Należy bowiem zadbać o to, by wektory te znajdowały się w tej samej przestrzeni. W przeciwnym razie konieczne jest odpowiednie przekształcenie, co sprowadza się do pomnożenia przez jakąś macierz.
Aby łatwiej zorientować się, z którą przestrzenią mamy do czynienia, można stosować odpowiednie nazewnictwo, na przykład w postaci sufiksów. Przyrostki te mogą być przyporządkowane choćby następująco:
_o
(np. vPos_o
) niech odpowiada przestrzeni obiektu (object space), związanej z układem lokalnym konkretnej instancji modelu_w
(np. vPos_w
) może oznaczać globalną przestrzeń świata (world space)_v
to z kolei przestrzeń widoku (view space), związana z pozycją obserwatora_t
możemy przyporządkować do tangent space przy obliczeniach związanych z oświetleniem_l
może w końcu być przyrostkiem wektorów w przestrzeni związanej z pozycją konkretnego światłaOczywiście cały ten pomysł na kilometr śmierdzi sławetną notacją węgierską, jednak w tym przypadku “dekorowanie” nazw zmiennych ma znacznie głębszy sens. Błędów spowodowanych operacjami na wektorach z różnych przestrzeni nie wykryje nam bowiem żaden kompilator, a ich samodzielne wyśledzenie bywa bardzo trudne (podobnie jak większości innych błędów matematycznych). Dlatego należy zawsze zwracać baczną uwagę na to, z jaką przestrzenią aktualnie pracujemy, i stosować jeśli nie specjalne nazewnictwo, to chociaż szczegółowe i jednoznaczne komentarze.
Zauważyłem, że niektóre osoby mają problem z właściwym wykorzystaniem metod wirtualnych i polimorfizmu, nie widząc czasami możliwości ich wykorzystania nawet w najbardziej oczywistych przypadkach. Być może powodem jest coś w rodzaju braku zaufania do całego mechanizmu, który jest spowodowany wrażeniem, że kompilator dokonuje przy jego okazji jakichś pseudomagicznych sztuczek.
A tak naprawdę z metodami wirtualnymi sprawa jest dosyć prosta. Pomiędzy nimi a zwykłymi metodami różnica polega wyłącznie na sposobie ich wywoływania.
W tym pierwszym przypadku użycie zwykłej metody względem obiektu:
jest zamieniane w wynikowym kodzie na normalne wywołanie funkcji z dodatkowym parametrem:
Ten parametr jest potem dostępny jako wskaźnik this
wewnątrz metody i pozwala odwoływać się do innych składowych obiektu.
W przypadku metod wirtualnych wywołanie jest natomiast dwuetapowe i polega na wykorzystaniu pewnych dodatkowych informacji. Są nimi: tablica funkcji wirtualnych (zwana vtable) oraz wskaźnik na nią (czyli vptr). Tablica występuje tylko w jednej kopii na całą klasę i zawiera tyle elementów, ile funkcji wirtualnych klasa ta posiada. Jej elementami są po prostu adresy w pamięci tych właśnie funkcji: pierwsza funkcja wirtualna ma więc adres zapisany w elemencie vtable o indeksie 0, druga – o indeksie 1, itd.
Tablica metod wirtualnych i wskaźnik na nią (Źródło)
Vptr jest natomiast daną trzymaną wraz z każdym obiektem, podobnie jak zwykłe pola tego obiektu (często vptr jest umieszczany w pamięci tuż po nich). Jest on niczym innym, jak wskaźnikiem na vtable i służy obiektowi, gdy ten chce wywołać którąś ze swoich metod wirtualnych. Dokładniej wygląda to mniej więcej tak:
Widać więc, że polega to po prostu na pobraniu odpowiedniego adresu metody z vtable, kierując się indeksem ustalanym w czasie kompilacji, zwykle na podstawie kolejności deklaracji metod wirtualnych w bloku class
.
Dlaczego jednak raz może być wywołana wersja metody z klasy bazowej, a raz z pochodnej?… Tego nietrudno się już chyba domyślić: wszystko zależy od tego, na jaką tablicę pokazuje vptr. Jego ustawienie następuje automatycznie w trakcie tworzenia obiektu; wtedy wiadomo oczywiście, jakiego on jest typu. Potem jednak może on być dostępny zarówno przez wskaźnik do swojej klasy, jak i do klasy bazowej. W obu przypadkach metody wirtualne będą działały poprawnie, gdyż w ich wywołaniu będzie pośredniczył vptr.
Tak to wszystko wygląda, w wielkim skrócie rzecz jasna :) Całość nie jest może trywialna ze względu na pewne kombinacje ze wskaźnikami do funkcji, jednak nie zaszkodzi mieć przynajmniej ogólne pojęcie o tym, jak to właściwie działa. Istnieje szansa, że dzięki temu będziemy potrafili korzystać nieco lepiej z metod wirtualnych, a przy okazji trochę więcej rozumieć z tajemniczych dyskusji “ekspertów C++” przerzucających się takimi tajemniczymi terminami jak vtable i vptr ;-]
W Windows obsługa zdarzeń opiera się na koncepcji pętli komunikatów (message loop). Taka pętla powinna nieustannie kręcić się w każdym wątku, w którym tworzone są okna, i na bieżąco pobierać pojawiające się komunikaty o zdarzeniach. Dzięki temu użytkownik może przeprowadzać interakcje z interfejsem programu. Dlatego też wszelkie dłuższe działania (np. złożone obliczenia lub odczyt/zapis, zwłaszcza przez sieć) mogą sprawić, że aplikacja wyda się zawieszona. Pętla komunikatów nie będzie bowiem pobierać i przetwarzać gromadzących się w kolejce zdarzeń.
Typowym rozwiązaniem jest umieszczenie takich czasochłonnych operacji w osobnym wątku. Czasem jest to oczywiście niezbędne, lecz wielowątkowość, jak wiadomo, oprócz korzyści wprowadza dodatkowe problemy, które należy uwzględnić – jak choćby synchronizacja dostępu do danych.
Jeśli jednak nasza długa operacja daje się podzielić na kilka mniejszych, to możemy wówczas zastosować inne rozwiązanie. Można mianowicie co jakiś (krótki) czas pobierać oczekujące w kolejce komunikaty i zajmować się nimi, dzięki czemu interfejs będzie miał szansę zareagować na akcje użytkownika. Zazwyczaj oznacza to odpowiednio częste wywoływanie funkcji w rodzaju System.Windows.Forms.Application.DoEvents
z .NET lub TApplication.ProcessMessages
z VCL (Delphi). Jeśli na przykład dokonujemy jakichś obliczeń w pętli, wspomniane funkcje można wywoływać w każdym jej cyklu.
Jest nieco gorzej, jeśli piszemy z użyciem samego Windows API. Podobną funkcję musimy wtedy napisać sami. Nie jest to na szczęście trudne – należy po prostu “na chwilę” uruchomić naszą pętlę komunikatów, aby przetworzyła oczekujące zdarzenia:
Korzystamy z PeekMessage
, która sprawdza, czy w kolejce nie ma już więcej zdarzeń i informuje nas o tym. Trzeba tylko pamiętać, że wśród tych zdarzeń może pojawić się WM_QUIT
, który to komunikat oznacza konieczność zamknięcia aplikacji. Sposobem na jego obsłużenie może być chociażby wartość zwrócona z naszej funkcji; wywołujący będzie wówczas wiedział, że powinien zakończyć to, co aktualnie robi, gdyż program ma zostać zamknięty.
Oczywiście postać powyższej pętli powinna tak naprawdę być identyczna jak tej głównej, w funkcji WinMain
– a więc niekoniecznie taka, jak powyżej. To się aczkolwiek zdarza wtedy, gdy korzystamy z okien dialogowych, akceleratorów menu i tym podobnych rzeczy. Samo to wymaga jednak na tyle dużej biegłości w Windows API, że odpowiednie przerobienie zaprezentowanej funkcji to przy tym naprawdę mały pikuś ;-)
Jeśli posługujemy się PowerShellem, to pewnie po jakimś czasie zechcemy dodać do niego jakiś nowy alias lub zupełnie nową komendę ogólnego przeznaczenia. W takim przypadku warto wiedzieć o istnieniu tak zwanego profilu, czyli skryptu ładowanego automatycznie przy starcie każdej sesji PS. Ścieżka do niego jest zawarta w zmiennej $profile i domyślnie ma formę:
Początkowo plik ten (ani nawet katalog, w którym się znajduje) nie istnieje, więc musimy go utworzyć. Gdy to zrobimy, będzie on pełnił funkcję powershellowego Autostartu.
Następnie możemy zechcieć coś w nim umieścić. Na początek można na przykład zmienić domyślny prompt (zwany czasem po “polskiemu” znakiem zachęty) powłoki na coś bardziej gustownego i przydatnego. W tym celu należy napisać funkcję prompt
, która jako rezultat zwracać będzie odpowiedni ciąg.
Fani linuksowych shelli mogą na przykład sprawić, aby PS wyglądał trochę jak bash przy pomocy następującej funkcji:
Wynik prezentuje się mniej więcej tak:
Można naturalnie poeksperymentować z dodaniem innych informacji niż nazwa komputera i na nim zalogowanego użytkownika. Możliwości są bowiem nieporównywalnie większe niż to, co oferuje chociażby stary cmd.exe.
Parę tygodni temu w wyniku spontanicznej akcji jednego z moderatorów na forum Warsztatu pojawił się dział o nazwie Szkółka. W założeniu mają tam trafiać wątki reprezentujące niższy poziom niż te, których miejsce jest w innych zakątkach forum. Mówiąc wprost, ma to być dział dla początkujących, jakich na Warsztacie nie jest wcale tak mało.
Przyznam, że początkowo byłem dość sceptycznie nastawiony do tego pomysłu. Szkółka powstała jako odtrutka na plagę pytań banalnych, oczywistych oraz takich, które zahaczają bardziej o podstawy programowania w ogóle niż programowania gier (a to drugie jest przecież zakresem tematycznym Warsztatu). Na pewno jednak nie miała być miejscem, gdzie toleruje się wybryki lamerskie, jak wątki typu “Oto mój kod, co tu jest źle?” czy pytania, na które odpowiedź można w 10 sekund znaleźć dowolną wyszukiwarką.
Moje wątpliwości dotyczyły tego, czy da się ten zamysł zrealizować w praktyce. Rzekoma Szkółka mogła bowiem łatwo stać się strefą wolnej amerykanki, gdzie dozwolone byłoby wszystko lub prawie wszystko.
Teraz jednak muszę stwierdzić, że ten eksperyment chyba jednak się powiódł. Korzyści z jego realizacji są całkiem spore: przede wszystkim jest to powolne zacieranie się wrażenia, iż na Warsztacie moderatorzy bez wahania banują każdego nowego użytkownika. (Co oczywiście było zawsze bardzo dalekie od prawdy, ale mniejsza o to ;P). Dodatkowo mamy też znacznie wyraźniejsze rozgraniczenie między początkującymi a lamerami dzięki przejrzystym zasadom moderacji w Szkółce. Dowolny wątek może bowiem być z niej usunięty tylko z powodów regulaminowo-netykietowych, a nie na podstawie rzekomo “zbyt niskiego poziomu” (którego ocena, nie ukrywajmy, jest zawsze w jakimś stopniu funkcją widzimisię danego moderatora).
Można oczywiście narzekać, że istnienie Szkółki obniża poziom Warsztatu jako całości. Argument ten opiera się na twierdzeniu, że ów wysoki poziom jest cenną wartością, którą należy usilnie chronić. Osobiście zupełnie się z tym nie zgadzam (o czym zresztą pisałem jakiś czas temu), więc ten postulat nie robi na mnie wielkiego wrażenia :) Za konieczną niedogodność istnienia Szkółki uważam jedynie to, iż przeglądając forum pod kątem ewentualnych interwencji moderatorskich (że tak eufemistycznie to nazwę ;]) należy zwracać uwagę na to, w którym dziale jest dany wątek. A to nie jest przecież jakoś specjalnie kłopotliwe.
Czasami zdarza się, że chcemy usunąć plik, który okazuje się być używany przez inny proces lub system operacyjny. Często nie mamy pojęcia, dlaczego tak jest i kto ma jakiś interes w trzymaniu blokady na tym właśnie pliku. Zwłaszcza Vista, będąca paranoicznie przewrażliwiona na punkcie zachowania integralności systemu plików, jest bardzo restrykcyjna pod względem swobody kasowania zbiorów będących w użyciu.
Oczywiście w takiej sytuacji można zawsze zrestartować system, ale trudno uznać to za wygodne rozwiązanie :) Dobrze byłoby więc mieć sposób na dowiedzenie się, jaki proces używa danego pliku, aby ewentualnie go zakończyć lub w skrajnym przypadku po prostu ubić.
I tu jest problem, bowiem w Windows nie ma na to prostego sposobu. Dotyczy to między innymi istnienia jakiegoś API, które by na to pozwalało; w najlepszym przypadku należy posłużyć się narzędziami z DDK, czyli pakietu służącego do… pisania sterowników działających w trybie jądra (kernel mode). Podobnie rzecz ma się z istnieniem wbudowanych w system programów, służących do tego celu (a więc czegoś podobnego do uniksowej komendy lsof). Odpowiednie narzędzia są jedynie częścią pakietów dodatkowych, jak Windows Server 2003 Resource Kit (niekompatybilny z Vistą) czy Windows Sysinternals. Mogą być one jednak dość kłopotliwe w obsłudze.
Dlatego podzielę się moim niedawnym odkryciem, którym jest mały i niezwykle przydatny programik o nazwie Unlocker. Potrafi on dokładnie to, o czym jest tu mowa: wylistować procesy, które używają danego pliku. Działa on przy tym na każdej sensownej wersji Windows, integruje się z menu kontekstowym plików dla większej wygody, no i jest do tego aplikacją freeware.
Rzadko robię czemuś aż taką “reklamę”, ale w tym przypadku uznałem, że tak dobrym i użytecznym narzędziem należy się niezwłocznie podzielić ze światem :)