Monthly archive for June, 2008

Konstruktory bezparametrowe

2008-06-28 12:12

W C++ konstruktor domyślny jest generowany automatycznie, jeśli w klasie nie zostanie zdefiniowany żaden inny. W przeciwnym wypadku nie jst on tworzony, co może prowadzić do sytuacji, gdy klasa posiada jedynie takie konstruktory, które wymagają podania parametrów. Jest ona niekorzystna przynajmniej z dwóch powodów:

  1. Gdy dziedziczymy po takiej klasie, musimy jakoś zapewnić, że w klasie pochodnej zostanie wywołany konstruktor klasy bazowej. Zwykle robi się to za pomocą listy inicjalizacyjnej:
    1. Derived(int param1, int param2) : Base(param1, param2) { /*... */ }

    Kiedy jednak zmieni się postać konstruktora klasy bazowej, wówczas modyfikacja będzie musiała dotyczyć wszystkich bezpośrednich potomków tej klasy. W nowej wersji standardu C++ ma być aczkolwiek wprowadzone nowe zadanie dla słowa kluczowego using, które pozwoli na automatyczne utworzenie konstruktorów “przekaźnikowych” – takich, których jedyną rolą jest wywołanie konstuktorów bazowych z takimi samymi parametrami jak te otrzymane w konstruktorze pochodnym. Jeśli jednak przy okazji chcemy coś zmienić czy pominąć, to nie ma rady: trzeba całą tę “sztafetę” zakodować ręcznie.

  2. Jeszcze gorzej jest, kiedy klasa bez konstruktora domyślnego jest wirtualną klasą bazową. Wówczas o jej prawidłowej inicjalizacji muszą pamiętać nie tylko klasy bezpośrednio pochodne, ale w ogóle wszystkie potomne! Dokładnie tak: nawet w piątym czy dziesiątym pokoleniu hierarchii o korzeniu w wirtualnej klasie bazowej, klasy pochodne muszą w swoich konstruktorach wywoływać konstruktory owego korzenia. To oczywiście sprawia, że każda zmiana musi być rozpropagowana na całe to drzewo dziedziczenia (a właściwie graf, bo pewnie zawiera on cykle :>).

Dlatego też klasy będące przede wszystkim klasami bazowymi w dziedziczeniu dobrze jest wyposażać w konstruktory bezparametrowe. Może to aczkolwiek wymagać wyróżnienia w obiekcie stanów Zainicjowany-Niezainicjowany, które powinny być sprawdzane w jego metodach i w razie potrzeby odpowiednio sygnalizowane.

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

Bo to zła instrukcja była…

2008-06-24 21:56

Ostatnio na forum Warsztatu wyrosła dyskusja wokół niepozornej instrukcji break. Zaczęło się od zasłyszanej opinii, że korzystanie z niej jest objawem złego stylu programowania i że jest generalnie niezalecane. Argumentem na rzecz takiego twierdzenia jest to, że break jest podobny w swoim działaniu do etykiet i “wyklętej” instrukcji goto:

  1. while (WarunekPetli())
  2. {
  3.     if (CosSieStalo()) goto ZaPetla;   // prawie jak break
  4. }
  5. ZaPetla:
  6.     // ...

W sumie wyszła z tego całkiem długa polemika, która ujawniła kilka ciekawych faktów. Po pierwsze: przejrzystość kodu jest w całkiem dużym stopniu kwestią subiektywną i o ile jednemu może przeszkadzać brak (lub niewystarczająca ilość) komentarzy, to dla drugiego o wiele ważniejsze może być używanie lub nieużywanie określonych konstrukcji językowych. Po drugie: oprócz wspomnianego już goto czy wywołanego we wspomnianym wątku breaka, do instrukcji potencjalnie niepożądanych swobodnie można zaliczyć bardzo wiele innych rozwiązań – zależnie od upodobań, poczucia ‘słuszności’, bieżącej fazy księżyca i pewnie mnóstwa jeszcze innych kryteriów. I tak dostało się chociażby pętlom “nieskończonym” (typu while(true) lub for(;;)), wspomniano coś o wyjątkach, a sam napomknąłem o instrukcji continue lub “przedwczesnym” return:

  1. void Funkcja()
  2. {
  3.     if (/* warunek wyjścia */) return;
  4.     // reszta instrukcji
  5. }

Teoretycznie można by wszystkie te konstrukcje usunąć w imię ideologicznej czystości programowania strukturalnego, w którym każdy blok ma dokładnie jedno wejście i wyjście. Ale tu na szczęście wkracza wniosek trzeci: języki programowania używane w praktyce nie są żadnymi doskonałymi modelami, nad którymi należy się zachwycać. To narzędzia i jako takie powinny być przede wszystkim wygodne w użytkowaniu.
Więc nie warto drzeć kotów o sens stosowania break czy nawet goto (która to instrukcja też ma swoje zastosowania) i wydawać ogólne sądy na temat ich ‘nieprofesjonalności’. Bo nawet jeśli ta czy inna instrukcja rzekomo jest zła, to korzystający z niej kod nadal może być dobry, czytelny i łatwy do modyfikacji… Łącznie z refaktoringiem, który tego czy innego breaka może przecież usunąć ;P

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

Znaki białe i bielsze

2008-06-22 22:23

W językach programowania i opisu (np. HTML lub XML) ważną rolę odgrywają tzw. białe znaki (whitespaces). Nie są one nigdy wypisywane ani drukowane jako symbole, ale zajmują miejsce na ekranie i czasem powodują dodatkowe efekty (jak na przykład przejście do nowego wiersza w przypadku znaku końca linii – line feed). W wielu przypadkach parsery tekstowe ignorują większą ilość tego typu znaków, pozwalając dzięki temu na dowolne formatowanie kodu. Ale czasami jest wręcz odwrotnie: istnieje mianowicie ezoteryczny język programowania – zwany oryginalnie Whitespace – w którym to właśnie znaki niedrukowane (a dokładniej spacja, tabulator i znak końca wiersza) są jedynymi, które są interpretowane jako kod.

Jakie jednak znaki należy uważać za niedrukowane?… Sprawa nie jest taka prosta, gdyż, podobnie jak rozróżnienie liter wielkich i małych, zależy ona od ustawień lokalnych języka. Większość języków programowania dysponuje aczkolwiek odpowiednią metodą sprawdzenia. W C++ jest to na przykład standardowa funkcja isspace. Ponieważ jednak w grę wchodzą ustawienia kulturowe, rezultaty zależą od języka systemu operacyjnego (przynajmniej teoretycznie).
W Unicode mamy na szczęście standardowo zdefiniowane, które znaki są traktowane jako białe. Te właśnie są sprawdzane przez metody w rodzaju java.lang.Character.isWhitepace w Javie czy System.Char.IsWhiteSpace w .NET. Działają one tak samo niezależnie od kontekstu kulturowego.

A co mamy zrobić, jeśli optujemy za ANSI? Wówczas możemy ustalić sobie swój własny zbiór znaków interpretowanych jako białe. Według mnie najlepszym zestawem jest następujący:

  1. bool IsCharWhitespace(char c)
  2. {
  3.     return (c == 0x09 || c == 0x0A || c == 0x0D || c == 0x20);
  4. }

Te znaki to kolejno: tabulator (poziomy) (\t), line feed (\n), znak powrotu karetki (carriage return, \r) i zwykła spacja. Za takim wyborem przemawia chociażby fakt, że właśnie te znaki są uważane w XML za białe i że obejmują różne standardy kodowania końca wiersza na różnych platformach. Dwa inne znaki: tabulator pionowy (0x0B) i form feed (0x0C) są jeszcze uważane za białe w C, ale stosuje się je chyba zbyt rzadko, aby trzeba się było nimi przejmować :)

Tags: ,
Author: Xion, posted under Programming » Comments Off on Znaki białe i bielsze

Triki z PowerShellem #5 – Skróty

2008-06-20 13:53

Pisząc skrypty w PowerShellu mamy dostęp do niemal całej platformy .NET, zawierającej grubo ponad dziesięć tysięcy klas, dlatego nieczęsto będziemy potrzebowali czegoś spoza tego wielkiego bogactwa. Ale takie przypadki też istnieją. Na szczęście PSh umożliwia też dostęp do innych narzędzi niż .NET – jak choćby do starych (nie)dobrych obiektów COM.
Przy ich pomocy można zrobić całkiem sporo, zwłaszcza jeśli chodzi o sterowanie aplikacjami, które udostępniają na zewnątrz odpowiednie interfejsy (a robi tak wiele większych programów, nie tylko Microsoftu). Ciekawsze jest chyba jednak łączenie technologii: “starego” COM-a i nowego .NET-a, aby razem zrobić coś użytecznego. Oto prosty przykład:

  1. # shortcuts.ps1
  2. # Szuka skrótów do nieistniejących plików w podanym katalogu
  3.  
  4. # Parametr: ścieżka do przeszukiwanego katalogu
  5. param([string]$path = ".")
  6.  
  7. # Szukamy plików skrótów
  8. $allLinks = [IO.Directory]::GetFiles($path, "*.lnk", [IO.SearchOption]::AllDirectories)
  9.  
  10. # Przeglądamy listę i szukamy nieistniejących
  11. $wsh = New-Object -ComObject WScript.Shell
  12. $invalidLinks = New-Object Collections.ArrayList
  13. foreach ($linkFile in $allLinks)
  14. {
  15.     $lnk = $wsh.CreateShortcut($linkFile)
  16.     if (-not ([IO.File]::Exists($lnk.TargetPath) -or [IO.Directory]::Exists($lnk.TargetPath)))
  17.         { $invalidLinks.Add($linkFile) | Out-Null }
  18. }
  19. return [string[]]$invalidLinks.ToArray()

Dziwnym trafem najprostsze przykłady użycia COM są zwykle związane z tworzeniem skrótów (plików .lnk) przy pomocy metody CreateShortcut obiektu COM o identyfikatorze WScript.Shell. Obiekt ten był dawniej używany w skryptach hosta systemu Windows (Windows Scripting Host) i, jak widać, nadal zdarza mu się być użytecznym ;-)
Co jednak robi powyższy skrypt? Otóż nie tworzy żadnych nowych skrótów, a wręcz przeciwnie: wskazuje kandydatów do usunięcia, czyli skróty pokazujące na nieistniejące pliki lub katalogi. Na wyjściu dostajemy ich zgrabną listę, którą możemy przekierować na przykład do polecenia Remove-Item.
A tak możemy sobie wyczyścić Menu Start:

  1. $startMenu = [Environment]::GetFolderPath([Environment+SpecialFolder]::StartMenu)
  2. . ./shortcut.ps1 -path $startMenu | Remove-Item

I pomyśleć, że kiedyś napisałem do tego cały osobny program… :)

Tags: ,
Author: Xion, posted under Applications » 1 comment

Debugowanie preprocesora

2008-06-18 22:48

Preprocesor w C++ to może i przestarzała, ale i całkiem fajna zabawka. Przynajmniej czasami – mimo dość ubogiej logiki – pozwala ona załatać choć niektóre braki języka. A niekiedy pozwala też na pewne sztuczki, jakie ciężko byłoby osiągnąć inaczej. W końcu, można go też używać do tego, w czym jest najlepszy: do automatycznego wklejania powtarzających się fragmentów kodu w wielu miejscach.
Ponieważ jednak preprocesor działa na kodzie jak na tekście, to rezultaty mogą być czasami zadziwiające – zwłaszcza dla kompilatora. Może on nam wówczas pokazać błąd w linijce, która jest całkowicie poprawna… Ale tylko z dokładnością do zawartego w niej makra, które po rozwinięciu generuje błąd składniowy lub semantyczny. Weźmy chociażby chyba najprostszy przypadek tego typu:

  1. #define COUNT 10;
  2. int buf[COUNT]; // błąd!

W nim łatwo o wykrycie pomyłki, lecz w rzeczywistych sytuacjach może to być zadanie nie lada. Zwłaszcza wtedy, gdy użyte makro jest skomplikowane, a my przecież nie możemy przełączyć preprocesor w tryb pracy krokowej i śledzić, jak jest ono rozwijane.

Możemy jednak przynajmniej podejrzeć ostateczne rezultaty. Wprawdzie domyślne każdy plik źródłowy po przetworzeniu przez preprocesor trafia od razu do kompilatora, jednak zachowanie to można łatwo zmienić. Możliwe jest na przykład zrzucenie wstępnie przetworzonego źródła do pliku, który to możemy potem przejrzeć i zobaczyć, w co ostatecznie nasze makra się zmieniły.
W Visual C++ takie zachowanie ustawia się we właściwościach projektu, na zakładce Configuration Properties > C/C++ > Preprocessor. Tam możemy wybrać, czy chcemy, by rezultaty działania preprocesora na plikach .cpp były zapisywane do osobnych plików (z rozszerzeniem .i). Wachlarz opcji umożliwia też określenie, czy mają być do nich dołączane numery linii lub komentarze.

Opcje preprocesora w Visual C++

Włączenie tych opcji bywa przydatne, jeśli dostajemy na linijkach kodu z użyciem makr dostajemy komunikaty o błędach kompilatora, które są zupełnie “z innej bajki”. Ponadto w ten sposób można też zobaczyć, jakie fragmenty kodu są kierowane do kompilacji za pomocą dyrektyw typu #ifdef, co też bywa przydatne.
Pamiętajmy jednak, że wygenerowane w ten sposób pliki .i są prawie zawsze bardzo, bardzo duże – rzędu kilku megabajtów – gdyż zawierają treść wszystkich dołączonych nagłówków. Dlatego, ze względu na szybkość budowania projektu, nie powinniśmy ich generować poza przypadkami debugowania preprocesora.

Tags: ,
Author: Xion, posted under Applications, Programming » Comments Off on Debugowanie preprocesora

Lis w operze

2008-06-16 16:47

Logo FirefoksaParę dni temu odbyła się premiera nowej wersji przeglądarki Opera, oznaczonej numerkiem 9.5. Z kolei jutro z wielką pompą opublikowana zostanie trzecia edycja Firefoksa. Przy tej zresztą okazji Mozilla organizuje Dzień Pobierania, z zamiarem ustanowienia rekordu ilości ściągnięć oprogramowania w ciągu jednej doby. (Mam aczkolwiek przeczucie, że prawdopodobne jest ustanowienie innego rekordu: najdłużej niedziałającego serwera pobierania ;]).
Zbieżność tych dwóch wydarzeń zapewne nie jest przypadkowa. Tym bardziej dziwi mnie to, że przy tej okazji zaprzysięgli fani któregoś z tych dwóch programów zajmują się głównie wykazywaniem w każdym możliwym miejscu, iż to właśnie ich ulubiona przeglądarka jest lepsza od tej drugiej. Naturalnie, argumentów jest przy tym mnóstwo: a to gesty myszki, a to współdzielenie zakładek i Ulubionych między komputerami, nie wspominając o tysiącach dostępnych rozszerzeń i wtyczek, coraz lepszych wynikach testów typu Acid3, wbudowanych klientach innych usług niż WWW, kilogramach skórek i pewnie wielu jeszcze innych zaletach. Część z nich może i jest ważna i interesująca. Rzecz w tym, że – statystycznie rzecz biorąc – nie obchodzą one psa z kulawą nogą.

Logo OperySmutna prawda jest taka, że nadal większość użytkowników Internetu korzysta z “jedynie słusznej” przeglądarki pewnej znanej skądinąd firmy, a o dywagacjach na temat wyższości innych nad jeszcze innymi nigdy tak naprawdę nie słyszeli i raczej nie mają zamiaru usłyszeć. A to z wielu powodów niepokojące, chociażby ze względu na bezpieczeństwo czy powszechność stosowania standardów sieciowych. Wzajemne zwalczanie się zwolenników przeglądarek “niezależnych” (że je tak umownie nazwę…) na pewno nie zaradzi temu, iż połowa internautów wciąż używa do przeglądania Sieci programów przestarzałych i dziurawych jak ser szwajcarski.
A bliskość premier nowych edycji dwóch bezpiecznych, wygodnych i nowoczesnych przeglądarek to przecież całkiem dobra okazja, żeby coś na to poradzić. Zamiast więc bezowocnie spierać się, czy Safari jest lepsze od Opery, a ta od Firefoksa czy Camino, można by wspólnie zwiększyć wysiłki na rzecz uświadamiania użytkownikom niesłusznie najpopularniejszych przeglądarek, że istnieją dla nich o wiele lepsze alternatywy. Cóż bowiem z tego, że oto “wszyscy” przerzucą się czy na to Firefoksa, Operę czy inny tego typu program, jeśli ci ‘wszyscy’ to tak naprawdę ledwie 1/3 internautów?…

Tags: , ,
Author: Xion, posted under Applications, Internet, Thoughts » 10 comments

Stos wywołań funkcji

2008-06-13 13:42

Jedną z bardziej przydatnych informacji podczas znajdowania przyczyn błędów wykonania jest śledzenie stosu (stack trace; właściwie to nie ma dobrego polskiego tłumaczenia, jak zwykle zresztą :]). Dzięki niemu możemy mieć informacje o kolejnych wywołaniach funkcji, które doprowadziły błędu wykonania. Zwykle stack trace jest dostępny z poziomu złapanego obiektu wyjątku – np. poprzez właściwość System.Exception.StackTrace w .NET czy metody java.lang.Throwable.get/printStackTrace w Javie.

Standardowa klasa exception w C++ nie posiada podobnej funkcjonalności, bo w tym języku stos nie jest automatycznie śledzony. Przy pewnym wysiłku możemy aczkolwiek zapewnić sobie podobną funkcjonalność – chociażby tak:

  1. typedef std::list<std::string> StackTrace;
  2.  
  3. class Trace
  4. {
  5.     private:
  6.         static StackTrace ms_Trace;
  7.  
  8.     public:
  9.         Trace(const std::string& item) { ms_Trace.push_back (item); }
  10.         ~Trace()    { ms_Trace.pop_back(); }
  11.  
  12.     friend const StackTrace& GetStackTrace()    { return ms_Trace; }
  13. };
  14. StackTrace Trace::ms_Trace;    // inicjalizacja pola statycznego (w .cpp)
  15.  
  16. // makro (dla Visual C++)
  17. #define GUARD Trace __trace(__FUNCTION__ " [" __FILE__ ", line " __LINE__ "]")

Przechowujemy po prostu globalną listę z danymi wszystkich wywoływanych funkcji. Elementy do niej dodawane są przy wejściu w każdą funkcję, która na początku ma makro GUARD:

  1. int Foo() { GUARD; /* ... */ }

zaś zdejmowane wtedy, gdy pomocniczy obiekt typu Trace wyjdzie poza swój lokalny zasięg – czyli po opuszczeniu funkcji. To sprawia, że na liście mamy zawsze informacje o wszystkich wywołaniach funkcji prowadzących do aktualnego miejsca (jakie one są, zależy od kompilatora; użyte wyżej makro __FUNCTION__ nie jest na przykład standardową częścią C++). Tworząc obiekt wyjątku, możemy więc zapisać kopię tej listy, by można ją było odczytać w bloku catch i wyświetlić.
Rozwiązanie nie jest oczywiście bardzo ładne, bo wymaga dodatkowej linijki w każdej funkcji. Zdaje się jednak, że nie ma żadnego sposobu, by tę niedogodność wyeliminować. Ja przynajmniej takiego nie znam :)

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


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