W bardzo dawnych czasach systemy operacyjne słabo radziły sobie z wielozadaniowością (niektóre zgoła wcale). Ostatnio jednak zmieniło się całkowicie; teraz niemal cała funkcjonalność systemu w rodzaju Windows czy Linux opiera się na działających w tle procesach (zwanych usługami lub demonami).
O ile w tych przypadkach ma to najczęściej sens, o tyle ciężko jest mi zrozumieć, dlaczego coraz więcej zupełnie zwyczajnych aplikacji “upiera się”, aby także działać w sposób ciągły. Owszem, w niektórych przypadkach jest to jak najbardziej uzasadnione – weźmy chociażby internetowe komunikatory. Wydaje mi się jednak, że widoczny tu trend idzie zdecydowanie zbyt daleko.
Mam mianowicie wrażenie, że obecność ikonki prawie każdej aplikacji w systemowym zasobniku (tray) stała się niemal obowiązkowa. Nie ma przy tym znaczenia, czy dany program (albo raczej jego główna część) jest uruchomiony czy nie. Dobrym pretekstem do takiej obecności bywa nawet czynność tak absurdalna, jak pilnowanie skojarzeń określonych typów plików z macierzystą aplikacją (domyślnie robi tak np. odtwarzacz Winamp. Najważniejsze jest bowiem to, aby po jedno- lub dwukrotnym kliknięciu w taką ikonkę uruchomił się właściwy program. Zupełnie jakby posiadanie skrótów na Pulpicie czy w menu Start nie było całkowicie wystarczające.
Co więcej, raz wystartowana aplikacja zwykle też nie daje się tak łatwo zamknąć. Bardzo popularna jest, przykładowo, zmiana zachowania przycisku Zamknij (lewy górny róg okna), który wcale aplikacji nie zamyka, a jedynie minimalizuje ją do ikonki we wspomnianym już zasobniku. Dopiero jawne wybranie opcji z menu pozwala faktycznie program zakończyć; w innym przypadku jest to po prostu zamknięcie udawane.
Jakie są tego skutki? Ano takie, że nawet czterordzeniowy procesor z gigabajtami pamięci operacyjnej może mieć ciężkie chwile w obsłudze kilkudziesięciu procesów i kilkuset wątków naraz, z których większość stanowią “niedomknięte” aplikacje. Prawdziwe kłopoty zaczynają się zaś wtedy, gdy chcemy uruchomić naprawdę wymagający program, jak choćby pełnoekranową grę.
A wszystko to dlatego, że aplikacje chcą być mądrzejsze od swoich użytkowników…
Jedną ze szczególnych cech jest C++ jest obecność różnych “nadmiarowych” właściwości, które pojawiły się w języku właściwie przypadkiem. Bardziej znanym przykładem jest chociażby metaprogramowanie za pomocą szablonów, czego pierwszym przykładem jest słynny kod generujący liczby pierwsze na wyjściu kompilatora. Z początku było to nic innego, jak niezamierzony skutek uboczny procesu rozwijania szablonów, acz teraz zostało to oczywiście świadomie rozwinięte.
Podobnie jest na przykład z preprocesorem, jako że C i C++ są jednymi z niewielu języków, które taki mechanizm posiadają. Na początku służył głównie do deklarowania “stałych”, potem także makr naśladujących funkcje… A przecież można go też użyć do niemal całkowitej zmiany składni języka – tak, by zaczął on udawać inny. Przykładowo, za pomocą kilku poniższych #define
‘ów:
#include
#include
#define IF if(
#define THEN )
#define BEGIN {
#define END }
#define WRITELN std::cout<<
#define READLN(x) std::getline(std::cin,x)[/cpp]
możemy sprawić, że w C++ będzie pisało się prawie jak w Pascalu:
[cpp]WRITELN (“Podaj hasło:”);
READLN (pass);
IF pass == “strasznietajnehaslo” THEN
BEGIN
WRITELN (“OK”);
END;[/cpp]
Nie wszystkie niuanse składni innego języka da się oczywiście zasymulować przy pomocy makr, ale w praktyce udaje się bardzo wiele. Mimo to polecam jednak programowanie w C++ przy pomocy C++, a nie Pascala czy Basica ;)
Kiedy kompilator wyrzuca nam jakiś błąd, mamy o tyle wygodnie, że wystarczy w niego dwukrotnie kliknąć i już w swoim środowisku programistycznym przenosimy się do miejsca, w którym wystąpił problem. To w sumie dość oczywiste i zwykle w ogóle się nad tym nie zastanawiamy.
Zdarza się jednak, że kłopotliwy fragment musimy odnaleźć samodzielnie, dysponując tylko numerem wiersza, w którym się znajduje. Tak jest chociażby wtedy, gdy sygnalizujemy błąd czasu wykonania, używając przy okazji C++’owych makr typu
__FILE__
i __LINE__
. Co wtedy – mamy szukać feralnego miejsca ręcznie?…
Na szczęście w każdym porządnym IDE istnieje opcja Go To Line, pozwalająca na szybki skok do wiersza kodu o danym numerze. W Visual Studio (i nie tylko) dostępna jest pod skrótem klawiszowym Ctrl+G. Niby nic nadzwyczajnego, ale rzecz okazuje się częstokroć niezmiernie przydatna… na przykład wtedy, gdy z różnych powodów kompilujemy projekt z wiersza poleceń :)
Ostatnio doszedłem do wniosku, że odpowiadanie na z pozoru trywialne pytania początkujących programistów (głównie na forum Warsztatu, rzecz jasna) nie jest w rzeczywistości takie proste. Zwykle nie chodzi oczywiście o samą naturę problemu, który w większości ogranicza się do “tajemniczego” (głównie dla pytającego) komunikatu kompilatora, narzekającego na taką-a-taką linijkę w kodzie. Owszem, rozwiązanie często widać na pierwszy rzut oka. Gorzej jest z odpowiednim jego przekazaniem.
Z jednej strony można odpowiedzieć krótko, zwięźle i precyzyjnie, trafiając w samo sedno sprawy bez zbędnych wyjaśnień. Wtedy jednak nierzadko okazuje się, że taka odpowiedź rodzi raczej dodatkowe pytania, chociażby ze względu na odruchowe posługiwanie się pewnymi bardziej zaawansowanymi pojęciami, które dla nowicjusza będą nieznane. A stąd już niedaleko do bycia postrzeganym jako zarozumiały mądrala.
Zamiast tego można oczywiście udzielić długiej i wyczerpującej odpowiedzi, omawiającej spory zakres zagadnienia, którego dotyka problem. Można – jeśli ktoś ma na to czas i ochotę ;P Przy okazji można też niestety skutecznie oduczyć delikwenta samodzielności w znajdowaniu rozwiązań swoich programistycznych problemów.
A więc i tak źle, i tak nie(zbyt )dobrze :) Ale nie jest to w sumie zaskakujące: przecież podobno nie ma niewłaściwych pytań, a jedynie takież odpowiedzi. A przedstawiony wyżej dylemat ma też jedną poważną zaletę: jest całkiem dobrym pretekstem, aby przestać zajmować się cudzymi kłopotami i wziąć się za własny kod ;]
Przedwczoraj w końcu udało mi się skończyć pewien “drobny” projekt, który – pomimo tego że był ściśle związany z programowaniem grafiki – nie należał wcale do lekkich, łatwych i przyjemnych. To pewnie dlatego, że chodzi tu o software’owy renderer, który (na)pisałem w Javie (!) jako projekt uczelniany. Niewykluczone, że niektórzy pamiętają jeszcze, o co chodzi ;-)
W wymaganiach przewidziana była na szczęście tylko jedna, za to konkretna scena – mianowicie pewien znany skądinąd most. W porównaniu z oryginałem nie wyszedł on może specjalnie imponująco, ale nie zapominajmy, że każdy z jego pikseli musiał zostać pieczołowicie wyliczony przez biedny procesor ;) Rezultaty prezentują się zaś następująco:
I wprawdzie tego nie widać, ale manipulowanie tą sceną (złożoną z kilkuset trójkątów) mogło się odbywać w sposób całkiem płynny – o ile oczywiście powiększenie nie było zbyt duże :) Tym niemniej nie wróżę jednak temu rozwiązaniu zbyt wielu praktycznych zastosowań. Co oczywiście ani na jotę nie zmienia faktu, że jego implementacja okazała się całkiem pouczająca. Jednym z praktycznych wniosków jest chociażby to, że modelowanie za pomocą ręcznego opisywania sceny we własnym formacie opartym na XML to zdecydowanie nie jest dobry pomysł ;]
Gdy w C++ tworzymy typ wymagający indeksowania więcej niż jednym indeksem – a więc coś w stylu wielowymiarowej tablicy, np. macierzy – zazwyczaj używa się do tego celu operatora nawiasów okrągłych. Nie jest to specjalnie spójne z tablicami wbudowanymi język, gdzie do indeksowania stosuje się nawiasy kwadratowe, w tym przypadku nawet więcej niż jedną parę.
O ile jednak da się przeciążyć operator []
, o tyle “operatorów” [][]
, [][][]
, itd. już nie. Można jednak zastosować inną technikę, jeśli chcemy by nasze własne typy były składniowo maksymalnie podobne do wbudowanych.
Trzeba mianowicie przygotować je tak, by dało się do nich stosować operator []
niejako więcej niż raz. Wymaga to wprowadzenia jakiejś klasy pośredniej; dla macierzy może ona reprezentować pojedynczy wiersz:
Dla tego wiersza piszemy naturalnie zwykły operator indeksowania, pozwalający nam dostać się do jego elementów. Trik leży w postaci operatora, którą umieszczamy w samej klasie macierzy:
Zwraca ona nasz wiersz, a właściwie jego opakowanie, które to zdefiniowaliśmy. W ten sposób osiągamy dla Matrix<T>
zachowanie niemal dokładnie analogiczne do tablic typu T**
: pierwsza para nawiasów daje nam T*
(u nas MatrixRow<T>
), zaś druga konkretną wartość typu T
:
W tym rozwiązaniu oczywiście parę szczegółów do uwzględnienia (np. warianty const
naszego operatora). Widać jednak, że jeśli bardzo chcemy, to przy odrobinie pomysłowości da się wszędzie używać “właściwych” nawiasów :)
Podobno odpowiedź na (niemal) każde pytanie znajduje się tuż obok – na drugim końcu sieciowej wyszukiwarki. Tak przynajmniej utrzymują uczestnicy wielu internetowych forów. Aby jednak ją uzyskać, należy owo (za)pytanie odpowiednio sformułować, co wbrew pozorom nie musi być wcale takie łatwe.
Paradoksalnie przeróżne “usprawnienia” wprowadzane w mechanizmach wyszukujących, które mają uczynić je bardziej “inteligentnymi” i “spersonalizowanymi”, są często nieprzydatne przy poszukiwaniu konkretnych rozwiązań – zwłaszcza w dziedzinie programowania. Wydaje mi się bowiem, że o wiele ważniejszy jest właściwy dobór słów kluczowych i ich odpowiednia precyzja. Nie za mała – by nie zostać zasypanym tysiącami rezultatów, ale i niezbyt duża, by wyszukiwarka miała przynajmniej kilkadziesiąt wyników do posortowania względem trafności.
Jak więc dobierać odpowiednie słowa? Nie kreuję się oczywiście na jakiegoś eksperta w tej sprawie, ale z doświadczenia wynika mi kilka poniższych reguł: