Posts tagged ‘C/C++’

Refleksje

2007-11-23 22:19

Dzisiaj zastanowimy się nad refleksjami. Nie, nie jest to wcale masło maślane. Mechanizm refleksji (reflection – odbicie) jest zwany też introspekcją i polega na tym, iż działający program “wie” o swojej wewnętrznej strukturze. Prostą odmianą jest tutaj dynamiczna informacja o typie (RTTI), czyli możliwość określenia faktycznego typu obiektu znanego poprzez wskaźnik lub referencję.
W bardziej zaawansowanej wersji, obecnej na przykład w języku Java i na platformie .NET, refleksje są jednak czymś więcej. Stanowią faktyczne ‘odbicie’ struktury programu w postaci informacji dostępnych w czasie wykonania. Przy ich pomocy można na przykład uzyskać dokładne informacje o polach, metodach i właściwościach każdej klasy oraz jej miejscu w hierarchii dziedziczenia, nie mówiąc już o możliwości tworzenia jej instancji. Dostępność takich informacji umożliwia łatwe stworzenie modułu serializacji lub bardziej zaawansowanego odśmiecania pamięci, bazującego na przechodzeniu grafu odwołań między obiektami.

Ale C++ posiada tylko bardzo proste RTTI, które do takich celów jest daleko niewystarczające. Czy można jednak naprawić tę przypadłość? Jest na to kilka sposobów:

  • Można spróbować odczytać informacje debugowania generowane przez nasz kompilator. To może dawać kompletne informacje o wszystkich typach użytych w programie, ale ma dwie zasadnicze wady. Po pierwsze, wymaga kompilacji programu przynajmniej z pewnymi ustawieniami trybu debugowania także w wersji wydaniowej. A po drugie, jest w oczywisty sposób nieprzenośnie pomiędzy różnymi kompilatorami. Poza tym nie wydaje mi się, żeby było to proste rozwiązanie.
  • Innym podejściem jest odczytanie wszystkich danych bezpośrednio, czyli samodzielne przeanalizowanie kodu źródłowego i wygenerowanie na tej podstawie koniecznych informacji. Najpewniej wynikiem byłyby dodatkowy kod poddawany normalnej kompilacji. Wadą jest tu stopień skomplikowania: parsowanie kodu C++ łącznie z rozwijaniem dyrektyw preprocesora zdecydowanie nie jest prostym zadaniem. Ponadto proces kompilacji projektu stałby się bardziej złożony.
  • Jeżeli ktoś byłby dostatecznie zdesperowany, mógłby teoretycznie… stworzyć własny kompilator C++, obsługujący refleksje natywnie. Nie da się ukryć, że potrzeba by sporej siły, by zamachnąć się motyką na takie słońce :)
  • Wreszcie możliwe jest, aby programista podawał niezbędne informacje – zwykle jako kombinacja dziedziczenia i makr preprocesora. To podejście jest (stosunkowo) najprostsze w implementacji, ale zapewne najbardziej nieporęczne w użytkowaniu. Może być też podatne na błędy. Jednocześnie jednak ma największe szanse być przenośne i… działające :)

Gdybym aczkolwiek kiedyś cierpiał na dużą nadwyżkę wolnego czasu, to pewnie rozważyłbym implementację rozwiązania drugiego (lub jakiejś kombinacji rozwiązania drugiego i pierwszego, na przykład zlecającej rozwijanie makr programowi kompilującemu). Do tego czasu jednak C++ już dawno będzie posiadał wbudowany system refleksji ;)

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

Tablice bardzo dynamiczne

2007-11-15 10:32

W każdym poważnym języku programowania mamy do dyspozycji tablice o zmiennym rozmiarze, możliwym do ustalenia w czasie działania programu. Teoretycznie można by się bez nich obyć, jeżeli język posiada jakąś formę wskaźnika czy referencji, lecz konieczność ciągłego używania list na pewno nie byłaby przyjemna.
Z drugiej strony tablice mogą też być indeksowane więcej niż jednym indeksem, czyli być wielowymiarowe. Niekiedy wszystkie podtablice w danym wymiarze muszą mieć ten sam rozmiar (czyli całość być prostokątem, prostopadłościanem, itd.), ale np. C++ i C# pozwalają na dowolność i tworzenie chociażby macierzy trójkątnych zawierających tylko interesujące nas komórki.

To całkiem nieźle, lecz nie spotkałem jeszcze żadnego języka, który osiągnąłby zen w kwestii dynamicznych tablic, a mianowicie umożliwiał zmianę liczby wymiarów w trakcie działania programu. Być może powodem jest fakt, że w przypadku takich wybitnie elastycznych tablic nie da się już zapewnić dostępu do pojedynczego elementu w czasie stałym. Dla n wymiarów może on bowiem wynieść O(n2).
Jak więc można taką bardzo dynamiczną tablicę symulować? Ano trzeba samemu przeprowadzić jej linearyzację, czyli ponumerowanie wszystkich elementów tak, by można było obliczyć adres każdego w – liniowej przecież – pamięci operacyjnej. Przykładowo dla tablicy dwuwymiarowej element (x, y) będzie miał zlinearyzowany indeks równy zwykle x + y * width. Numerując z kolei trójwymiarową “kostkę”, otrzymalibyśmy formułę: x + y * width + z * height * width.

I tak dalej… W przypadku ogólnym aczkolwiek wzór może być trochę straszny :) Dla n-wymiarów o rozmiarach c1, …, cn, element opisany indeksami a1, …, an (liczonymi od zera) w wersji zlinearyzowanej znajdzie się na miejscu l:

Wzór na linearyzację tablicy wielowymiarowej

Formuła przekłada się oczywiście na dwie zagnieżdżone pętle – stąd więc jej oczekiwana złożoność. Można ją jednak łatwo zoptymalizować, przechowując wartość wewnętrznego iloczynu dla każdego z n wymiarów. Wtedy dostęp będzie już w czasie liniowym – oczywiście cały czas względem liczby wymiarów, a nie rozmiaru tablicy, więc nie jest aż tak źle :)
Taką dynamicznie wielowymiarową tablicę prawdopodobnie najłatwiej i najlepiej jest zaimplementować C++. I, co ciekawsze, nie będzie to wcale odkrywanie koła na nowo – czegoś takiego nie ma bowiem ani STL (daleko jej do tego), ani nawet boski boost ;]

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

Bolączki C++ #6 – Obsługa zdarzeń

2007-11-09 23:46

Dzisiaj w programowaniu aplikacji obowiązują dwie proste i fundamentalne zasady. Po pierwsze, programujemy obiektowo i kod zamykamy w klasy z polami i metodami. Po drugie, tworzymy programy działające w środowisku graficznym, z okienkami, przyciskami, polami tekstowymi i innymi klasycznymi elementami interfejsu.
Jednak jeden plus dwa równa się kłopoty, przynajmniej w C++. Już raz narzekałem zresztą na to, że w tym języku obiektowość i GUI to nie jest najlepsze połączenie. Zgrzyta tu mechanizm obsługi zdarzeń generowanych przez interfejs graficzny.

Wcześniej napisałem, że możliwym rozwiązaniem problemu jest zasymulowanie w jakiś sposób delegatów, czyli – z grubsza – wskaźników na metody obiektów. To jedna z dróg radzenia sobie z kwestią obsługi zdarzeń. Ale też inna, wykorzystująca mechanizm metod wirtualnych i polimorfizmu. Polega to na zdefiniowaniu jednolitej klasy bazowej dla obiektów, które mają odbierać zdarzenia. Zwie się je zwykle event handlers, co jak zwykle nie ma dobrego tłumaczenia. Taki handler wyglądać może na przykład tak:

  1. class IEventHandler
  2. {
  3.    public:
  4.       virtual void OnClick(IControl* pSender) = 0;
  5.       virtual void OnKeyDown(IControl* pSender) = 0;
  6.       // itd.
  7. };

Mając jakiś element interfejsu użytkownika, np. przycisk, przekazujemy mu nasz własny obiekt implementujący powyższy interfejs. Metody tego obiekty są następnie (polimorficznie) wywoływane w reakcji na odpowiednie zdarzenia.
Tak oczywiście można robić w C++, ale nie jest to zbyt wygodne. Tym czego znów brakuje, to niestatyczne klasy wewnętrzne, obecne choćby w Javie, których brak w C++ nie da się do końca zastąpić wielokrotnym dziedziczeniem.

Albo delegaty, albo sposób opisany przed chwilą – zapewne nie ma żadnej innej drogi obiektowej obsługi zdarzeń. Niestety, żaden z tych sposobów nie jest obecnie wspierany w C++ i nie zanosi się, by miało się to wkrótce zmienić.

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

Semantyczne unikaty

2007-10-14 22:49

W języku angielskim istnieje bardzo ciekawe słowo ‘serendipity‘. W skrócie, oznacza ono “umiejętność” dokonywania szczęśliwych i ważnych odkryć całkowicie przez przypadek – w szczególności wtedy, gdy tak naprawdę szukaliśmy czegoś zupełnie innego. Najciekawszą cechą tego słowa jest fakt, że bardzo trudno przełożyć je na język inny niż angielski przy pomocy czegoś krótszego niż podana wcześniej definicja (a już na pewno nie poprzez jeden wyraz). Dlatego też w 2004 roku znalazło się ono w czołówce najtrudniejszych do przetłumaczenia słów.

Czy w językach programowania możemy spotkać się z czymś podobny? Istnieją oczywiście tzw. idiomy, nakazujące by określone czynności robić tak, a nie inaczej – jak choćby słynny idom eraseremove z STL. Jednak tutaj chodzi mi raczej o taki element języka, który ułatwia życie albo po prostu jest w jakiś sposób nietypowy i – co najważniejsze – nie jest po prostu cukierkiem składniowym: jego przełożenie na inny język wymagałoby większej ilości kodu lub byłoby po prostu niemożliwe.
Jeżeli odpowiedź to pytanie jest twierdząca, to moimi osobistymi typami są:

  • Pętla for w C++. Można ją rzecz jasna z powodzeniem symulować we wszystkich językach proceduralnych przy pomocy pętli typu while, lecz jej elastyczność jest naprawdę zadziwiająca. Prawie komicznie wygląda pętla, w której wszystkie operacje wykonywane są w nagłówku, a jej zasadnicza treść jest pustym blokiem
  • “Funkcja” list, pozwalająca rozdzielić elementy tablicy pomiędzy ustalone zmienne za pomocą jednej instrukcji. Przydatna choćby przy przetwarzaniu rekordów baz danych czy parametrów GET i POST. Jej odpowiednikiem w innych językach jest po prostu odpowiednia seria przypisań.
    1. $tab = array("one", "two", "three");   // tablica
    2. list($one, $two, $three) = $tab;   // rozdzielenie elementów na zmienne
  • Funkcja Mid z Visual Basica. Zasadniczo jej celem jest wyodrębnienie fragmentu łańcucha znaków i zwrócenie go, lecz może ona występować też po prawej stronie operatora przypisania. Wówczas służy ona do zastąpienia tegoż fragmentu innym tekstem, np.:
    1. Dim str As String
    2. str = "Ala ma kota"
    3. Mid(str, 5, 2) = "nie ma" ' i Ala już nie ma kota :)
  • Sekcje initalization i finalization w modułach Delphi. Zawarty w nich kod jest wykonywany jednokrotnie, odpowiednio: przy załadowaniu modułu oraz przy “sprzątaniu” w trakcie kończenia aplikacji. Nie jest to za bardzo przydatne w kodzie obiektowym – tym bardziej używającym odśmiecacza pamięci – ale podobny mechanizm występuje też w języku Python.
  • I wreszcie mój ulubiony trik, czyli tzw. swizzling z HLSL (a także shaderowego asemblera). Jest to sprytna i efektywna technika dostępu do składowych czteroelementowego wektora, pozwalająca dokonywać kilku operacji odczytu i/lub zapisu naraz. Wygląda to tak, że po kropce zamiast jednej literki oznaczającej składową (x, y, z lub w) podajemy kilka:
    1. v1.xyz = v1.w;  // wpisanie w do x, y i z
    2. v2.wzyx = v3.xyzw;  // "odwrócenie" wektora
    3. v4.xy = v4.zw;  // x = z i y = w

    Na pierwszy rzut oka może to wyglądać nieco zagadkowo, ale warto tej konstrukcji używać, gdyż karty graficzne dokonują swizzlingu albo za darmo, albo bardzo małym kosztem Zatem mamy tutaj elegancję, tajemniczość i efektywność w jednym, czyli to co programiści lubią najbardziej ;)

Ta lista na pewno jest o wiele za krótka. Jestem pewien, że programiści innych języków – tych co bardziej egzotycznych – mogliby dodać mnóstwo własnych typów.

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

Bolączki C++ #5 – Szablonowe typedef

2007-10-10 21:32

Szablony to bardzo potężna część języka C++. Można przynajmniej powiedzieć, że spośród podobnych mechanizmów obecnych w wielu innych językach (jak np. typy generyczne z C# czy parametryzowane z Javy), daje on zdecydowanie największe możliwości. Ale, jak wiadomo, wszędzie można zawsze coś poprawić :)

Jednym z takich mankamentów jest choćby to, że póki co szablonowe – czyli opatrzone frazą template <...> – mogą być tylko funkcje i klasy (pomijając dość specyficzny przypadek szablonowych deklaracji przyjaźni). To może się wydawać absolutnie wystarczające, ale istnieje jeszcze jeden potencjalny kandydat na “uszablonowienie”. Chodzi tu o deklarację typedef.
Przydatność takiego ‘szablonowego typedef‘ ukazuje się choćby w tym – przyznam że generalnie niezbyt mądrym – przykładzie tablicy dwuwymiarowej:

  1. vector<vector<int> > mtxInts;
  2. vector<vector<float> > mtxFloats;

Nieco bardziej życiowy przypadek to zdefiniowanie własnego alokatora STL i chęć użycia go w standardowych kontenerach:

  1. list<int, my_alloc<int> > lstInts;
  2. list<string, my_alloc<int> > lstString;

Widać, że w obu sytuacjach pewne fragmenty nazw typów powtarzają się, co zresztą sprawia, że nazwy stają się dość długie. Są to więc doskonali kandydaci do aliasowania poprzez typedef, lecz jest tu jeden feler: w takim aliasie musimy dokładnie wyspecjalizować typ, któremu nadajemy nową nazwę. Nie można zatem zostawić sobie pewnej swobody w sposób, jaki się od razu narzuca:

  1. // źle!
  2. template <typename T>
  3.    typedef list<T, my_alloc<T> > my_list;
  4. // użycie: my_list<int> lstInts;

Po prostu w aktualnej wersji C++ nie ma szablonowego typedef. Obecnie można tylko zastosować pewien substytut, posługując się zwykłą definicją szablonu klasy:

  1. template <typename _T> struct my_list
  2. {
  3.    typedef list<_T my_alloc<_T> > T;
  4. };

która sprawi, że np. my_list::T będzie naszą listą intów z niestandardowym alokatorem.
Niezbyt to zgrabne, ale na razie musi wystarczyć. Poprawy życia pod tym względem należy spodziewać wraz z C++0x, acz proponowana składnia “szablonowego typedef” jest nieco inna od tej, którą zaprezentowałem wyżej.

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

Bolączki C++ #4 – Właściwości

2007-09-29 13:14

W teorii OOPu klasa może składać się wielu różnych rodzajów elementów. Mamy więc pola, metody, właściwości, operatory, typy, zdarzenia czy nawet sygnały (cokolwiek to oznacza). Z drugiej reprezentacja obiektu w pamięci operacyjnej działającego programu to niemal wyłącznie wartości jego pól (z drobną poprawką na ewentualną tablicę metod wirtualnych).
To są dwie skrajności, a między nimi mam wszystkie obiektowe języki programowania. Jedne oferują w tym zakresie więcej, inne mniej. Weźmy na przykład C++.

Oprócz niezbędnych pól i metod pozwala on definiować przeciążone operatory i typy wewnętrzne. Nie posiada za to niezwykle przyjemnego “cukierka składniowego”, czyli właściwości. Z punktu widzenia programisty właściwości to takie elementy interfejsu klasy, który wyglądają jak pola. Różnica polega na tym, że dostęp do właściwości nie musi oznaczać bezpośredniego odwołania do pamięci, lecz może mu towarzyszyć dodatkowy kod – na przykład sprawdzający poprawność ustawianej wartości.
Prawdopodobnie najbardziej elastyczny mechanizm właściwości wśród popularnych języków programowania ma C#. Tam kod wykonywany przy pobieraniu i ustawianiu właściwości pisze się bezpośrednio w jej deklaracji:

  1. class Fraction
  2. {
  3.    private int num;
  4.    private int denom;
  5.  
  6.    // właściwość - mianownik ułamka
  7.    public int Denominator
  8.    {
  9.       get { return denom; }
  10.       set
  11.       {
  12.          if (value == 0)   throw new DivideByZeroException();
  13.          denom = Math.Abs(value);
  14.          num *= Math.Sign(value);
  15.       }
  16.    }
  17. }

Nieco gorzej jest w Delphi czy Visual C++, gdzie istnieje deklaracja __declspec(property). Tam trzeba napisać odpowiednie metody służące pobieraniu/ustawianiu danej wartości (akcesory) i wskazać je w deklaracji właściwości.
Natomiast w czystym C++ rzeczone akcesory – metody Get/Set – stosowane bezpośrednio są jedynym wyjściem. Niezbyt ładnym rzecz jasna.

Bez właściwości można się obyć, a ich wprowadzenie do języka pewnie nie byłoby takie proste. Pomyślmy na przykład, jak miałyby się one do wskaźników i referencji: o ile pobranie adresu właściwości nie ma sensu, o tyle przekazywanie jej przez referencję byłoby z pewnością przydatne.
Dlatego chociaż akcesory wyglądają brzydko, pewnie jest przez długi czas będą jedyną opcją. Na pocieszenie dodam, że programiści Javy są pod tym względem w identycznej sytuacji :)

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

Słowo kluczowe ‘base’

2007-09-24 17:51

Z dobrodziejstwa metod wirtualnych po prostu nie można nie korzystać. Dzięki nim kod jest bardziej elegancki, krótki, często (wbrew powszechnej opinii) efektywniejszy i naturalnie bardziej obiektowy :) Wszystkie te zalety opierają się oczywiście na tym, że tak naprawdę nie musimy wiedzieć, jaką wersję metody wirtualnej – oryginalną czy nadpisaną w klasie pochodnej – wywołujemy w danym przypadku.
Sama metoda aczkolwiek ‘wie’ to doskonale. Czasami zdarza się jednak, że chcielibyśmy wywołać jej odziedziczoną wersję, pochodzącą z klasy bazowej. Podobnie jak większość języków, C++ nie czyni tego automatycznie (z wyjątkiem konstruktorów i destruktorów), jako że nie zawsze jest to potrzebne. Ale nierzadko się przydaje i jest wygodne.

W wielu językach, jak choćby Delphi czy C#, mamy pomocnicze słowa kluczowe, służące do takich właśnie wywołań. W przeciwieństwie do nich C++ oferuje jednak dziedziczenie wielokrotne, wobec tego czasami klasa bazowa nie jest określona jednoznacznie. Dlatego też chcąc wywołać odziedziczoną wersję metody, musimy jawnie użyć nazwy tej klasy., np.:

  1. class CFoo { public: virtual void Do() { /* ... */ } };
  2.  
  3. class CBar : public CFoo
  4. {
  5.    public:
  6.       void Do()
  7.       {
  8.          // ...
  9.          CFoo::Do();   // wywołanie odziedziczonej wersji metody
  10.       }
  11. };

Wielodziedziczenia używamy jednak rzadko i w zdecydowanej większości sytuacji klasa bazowa będzie tylko jedna. Na takie okazje Visual C++ przewidział własne słowo kluczowe __super. Możemy też pokusić się o bardziej przenośne rozwiązanie, definiując taki oto szablon:

  1. template <class T> class Inherits : public T
  2. {
  3.    protected:
  4.       typedef T base;
  5. };

a wówczas zyskamy swoje własne “słowo kluczowe” base o możliwościach podobnych do tych z C#:

  1. class CBar : public Inherits<CFoo>
  2. {
  3.    public:
  4.       void Do()   { /* ... */   base::Do(); }
  5. };

Na nieszczęście jest tu mnóstwo różnych “ale”. Największym problemem jest to, że właściwej klasy bazowej (tutaj CFoo) nie ma jak zainicjalizować w klasie pochodnej, wobec czego musi ona dysponować domyślnym konstruktorem, który na dodatek będzie używany zawsze. To poważny feler, którego nie ma za bardzo jak naprawić. Dlatego jeśli bardzo doskwiera nam brak słowa base, to chyba jedynym sposobem jest… ręczne dodawanie typedefa podobnego do tego w szablonie Inherits.
Dopiero C++0x wprowadzi możliwość dziedziczenia konstruktorów (na zasadzie przekierowywania ich parametrów do klasy bazowej), która pozwoli wyeliminować wspomniane ograniczenie. Wówczas taka wersja szablonu:

  1. template <class T> class Inherits : public T
  2. {
  3.    protected:
  4.       typedef T base;
  5.    public:
  6.       // polecenie odziedziczenia konstruktorów z klasy T
  7.       using T::T;
  8. };

powinna zdać egzamin dla dowolnej klasy T.

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Słowo kluczowe ‘base’
 


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