Posts tagged ‘C/C++’

‘No i co tu jest źle?’ w wersji książkowej

2008-02-02 17:57

Kiedy ma się dość pokaźną biblioteczkę książek, czasami natrafia się na pozycję, która nie wiadomo skąd się w niej wzięła. Pytanie takie staje się tym bardziej uzasadnione, gdy rzeczoną książkę otworzymy, przekartkujemy i pobieżnie przeglądniemy, by po krótkim czasie uznać, że najchętniej widzielibyśmy ją w… punkcie skupu makulatury :P
Okładka książki “Jak NIE programować w C++”Coś takiego przytrafiło mi się zupełnie niedawno. Książką o której mam taką “pochlebną” opinię, jest dziełko opatrzone tytułem Jak NIE programować w C++. Jeszcze ciekawszy jest chyba podtytuł, który mówi, że wewnątrz znajdziemy dokładnie 111 programów z błędami oraz trzy działające. Statystyka jest imponująca, ale o co tak naprawdę tutaj chodzi?… Autor przedstawia nam mianowicie coś w rodzaju zbioru koderskich zagadek do samodzielnego rozwiązania, które polegają oczywiście na znalezieniu błędu w przedstawionym kawałku kodu.

Jak podejrzewam, celem tej książki w zamyśle autora było ustrzeżenie programistów C++ przez różnego rodzaju typowymi błędami, jakie mogą się zakraść do pisanego przez nich kodu. Cel to chwalebny, chociaż dość utopijny – w końcu nie wystarczy wiedzieć, na czym błąd polega, aby w magiczny sposób już nigdy więcej go nie popełnić. Trochę więcej wątpliwości mam natomiast co do obranej metody. Nie wiem, w jaki sposób obejrzenie ponad stu błędnych kodów ma sprawić, że będziemy częściej pisali poprawny kod. Spodziewałbym się raczej podświadomego nauczenia się prezentowanych tam złych przykładów i ich spontanicznego stosowania w rzeczywistych programach, co raczej nie ułatwiłoby nikomu pracy :)
Byłoby oczywiście świetnie, gdyby przykłady te były jedynymi niepoprawnymi kawałkami kodów, z którymi przychodzi nam się mierzyć. Ale przecież jest to odległe od prawdy o całe lata świetlne. Jako koderzy sami nieuchronnie produkujemy niepoprawny kod, który co rusz musimy korygować. Prawdopodobnie więc nie potrzebujemy dodatkowych łamigłówek tego rodzaju, bo wystarczą nam te, które w nieumyślny sposób tworzymy sami dla siebie. I niestety nie możemy w ich przypadku – w przeciwieństwie do kodów z książki – zajrzeć do części końcowej po wskazówki i odpowiedzi.
Jednak nawet wobec takich mankamentów, prezentowane w książce przykłady mogłyby mieć pewną wartość poznawczą. Rzecz w tym, że naprawdę interesujące zagadki można bez trudu policzyć na palcach obu rąk. Pozostałe są albo pomyłkami aż do bólu klasycznymi (na czele z pomyleniem operatora przypisania i równości w warunku logicznym), albo trywialnymi literówkami, albo świadectwami na – delikatnie mówiąc – niekompletną znajomość języka. (Moim faworytem jest deklaracja int n = 1,000,000;, w założeniu inicjująca zmienną wartością równą milion).

Aż chce się zapytać, czy są w tej książce jakieś cechy pozytywne. Odpowiadam prędko, że jak najbardziej, tyle że mają się one nijak do jej zasadniczej treści. Do każdej zagadki autor dołączył bowiem krótką, zabawną historyjkę “z życia wziętą” lub inny śmieszny tekst – wszystko oczywiście z dziedziny IT. Paradoksalnie więc ta “książka o C++” lepiej sprawdza się jako książka z dowcipami.
Jest też druga dobra strona. Przypomniałem sobie mianowicie, skąd u mnie wzięła się ta dziwna pozycja. Otóż zakupiłem ją podczas jakichś tragów wydawniczych, które akurat odbywały się na uczelni, zapłaciwszy za nią około dziesięć złotych. Nietrudno przeboleć taką niewielką sumę, nawet mimo tego, że nikomu nie poleciłbym wydania na tę książkę nawet jednej złotówki :)

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

Własny operator rzutowania

2008-01-30 22:27

W C++ nadal można używać “tradycyjnego” rzutowania w postaci (typ)wyrażenie, bo cały czas zachowywana jest przynajmniej częściowa kompatybilność wstecz ze starym C. Jak wiadomo jednak, zaleca się raczej stosowanie nowych (tzn. wprowadzonych w C++; faktycznie są już one raczej stare) operatorów rzutowania, czyli static_cast, dynamic_cast, reinterpet_cast i const_cast. Jest tak między innymi dlatego, iż pozwalają one rozróżnić rzutowania bezpieczne – które można sprawdzić w czasie kompilacji i które zawsze “mają sens” – od pozostałych.
Składnia takiego rzutowania to oczywiście something_cast<typ>(wyrażenie). Jeśli przyjrzymy się jej bliżej, to zauważymy interesującą rzecz. Otóż jest to bardzo podobne do pewnej formy wywołania szablonu funkcji. Ten fakt plus pewne zasady dotyczące dedukcji argumentów takich szablonów sprawiają, że zasadniczo możemy w C++ tworzyć… własne operatory rzutowania.

Prawdopodobnie najbardziej znanym jest implicit_cast, czyli operator rzutowania niejawnego. Możemy go zdefiniować następująco:

  1. template <typename Dest, typename Src>
  2. inline Dest implicit_cast(const Src& arg)
  3. {
  4.    return arg;
  5. }

i wywoływać w bardzo porządny sposób:

  1. string str = implicit_cast<string>("Hello world");

Ze strony swojego dzialania operator ten nie jest specjalnie ciekawy, bo to przykład językowego purytanizmu czystej wody. implicit_cast wykonuje bowiem te konwersje, które kompilator i tak by przeprowadził niejawnie; widać to zresztą po kodzie odpowiadającej mu funkcji.
No właśnie – warto zauważyć, że jest w gruncie rzeczy zwykła funkcja szablonowa. Działa ona jak operator rzutowania, gdyż drugi z argumentów tego szablonu – typ źródłowy – może zostać wydedukowany z argumentu funkcji (czyli wyrażenia, które rzutujemy). Musimy jedynie podać pierwszy z nich, czyli typ docelowy – ale tego można się było spodziewać. Trzeba zaznaczyć, że kolejność obu parametrów szablonu musi być właśnie taka, ponieważ ich przestawienie spowodowałoby, że automatyczna dedukcja nie mogła by zostać przeprowadzona. Zachodzi ona bowiem począwszy od końcowych parametrów.

Prawdopodobnie taki własny operator rzutowania to przede wszystkim efektowna sztuczka. Nic więc dziwnego, że kilka przykładów (np. lexical_cast) jest częścią biblioteki Boost, która niemal w całości jest zbiorem takich językowych gadżetów :) Sam aczkolwiek wymyśliłem dosyć dawno temu operator “rzutowania bezwzględnego”, który wygląda mniej więcej tak:

  1. template <typename Dest, typename Src>
  2. inline Dest absolute_cast(const Src& arg)
  3. {
  4.    return *(reinterpret_cast<Dest*>(const_cast<Src*>(&arg)));
  5. }

Służy on do dosłownego rzutowania dowolnego typu na dowolny inny w sensie zmiany interpretacji bajtów. Zdaje się on działać dla większości typów w C++ (głównym wyjątkiem są typy referencyjne, co nie jest specjalną niespodzianką) i bywa całkiem przydatny.

Naturalnie można wymyślić jeszcze sporo innych niestandardowych operatorów rzutowania, jak choćby konwersje między napisami w różnych kodowaniach (ANSI, Unicode) czy do/z jakichś własnych typów, opakowujących klasy zewnętrzne. W każdym razie widać znowu (i pewnie nie ostatni raz), że C++ pozwala nam na bardzo dużo, skoro nawet te nieco rozwlekłe *_casty można jakoś oswoić do własnych potrzeb :]

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Własny operator rzutowania

Z unikodem lub bez

2008-01-11 18:10

Unicode to ciekawy wynalazek. Zamiast stosować wymyślne sposoby na “przełączanie” sposobu interpretowania zwykłych 8-bitowych znaków, ktoś mądry wymyślił po prostu, że obecnie nie ma większych przeciwwskazań, aby zwykły tekst zajmował dwa razy więcej miejsca niż dotychczas. Powstał więc standard UTF-16, w którym stron kodowych nie ma, a każdy znak jest zapisany za pomocą jednego z 65536 kodów.
Ale jak to zwykle bywa z rozwiązaniami, które mają rozwiązywać istniejące problemy, Unicode natychmiast stworzył swoje własne :) Oprócz tego, że założone 16 bitów szybko okazało się za małe (stąd istnienie także UTF-32), powstał też szereg kłopotów praktycznych. Jednym z nich jest choćby to, że żyjemy w okresie przejściowym (który zresztą trwa już wybitnie długo) i że w użyciu jest zarówno unikod, jak i zwykłe ANSI. A na pierwszy rzut oka (ludzkiego i programowego) tekst jest po prostu ciągiem bajtów i bez zewnętrznych wskazówek nie jest możliwe określenie w stu procentach, czy został on zapisany w ANSI, UTF-8, UTF-16 czy UTF-32. Co więcej, znaki Unicode są oczywiście liczbami, a ponieważ zasadniczo zajmują one więcej niż jeden bajt, pojawia się problem z ustaleniem właściwej kolejności tych bajtów (little-endian lub big-endian).
Oba te problemy ma rozwiązywać tzw. znacznik porządku bajtów (Byte Order Mark), ale oczywiście jak większość dobrych praktyk, także i jego umieszczanie na początku dokumentów nie jest zbyt popularne :)

Z programistycznego punktu widzenia sprawa nie wygląda aczkolwiek aż tak źle i w większości przypadków jesteśmy w jednej z dwóch sytuacji. Pierwsza z nich to “nieświadome” korzystanie z unikodu (czy może raczej “szerokich”, dwubajtowych znaków), bo został o niego oparty używany przez nas język oraz platforma; najlepszymi przykładami są tu .NET i C# oraz Java.
Druga sytuacja to możliwość wyboru, z jakiego systemu będziemy korzystali. Bardzo dobre jest to, że prawie zawsze możemy zdecydować się na… oba, czyli potencjalne kompilowanie dwóch wersji: ANSI i Unicode. Pod Windows na przykład w programach pisanych w C++ wystarczy przestrzegać kilku prostych i dobrze znanych zasad:

  • Zamiast typów char (lub wchar_t) należy – wszędzie tam, gdzie chodzi nam o znaki – używać typu TCHAR, który w zależności od wersji zostanie zamieniony na jeden z tych dwóch.
  • Stałe napisowe należy wpisywać z użyciem makra TEXT (czyli TEXT("Coś") zamiast po prostu "Coś"), dzięki czemu zostaną one skompilowane jako łańcuchy ANSI lub Unicode.
  • Nie martwić się o to, jakich znaków używa systemowe API, bowiem wywołania funkcji systemowych zostaną przemapowane podczas kompilacji na właściwe wersje. Dotyczy to także niektórych bibliotek, jak chociażby DirectX.

Niestety, słowo ‘niektóre’ sytuuje się dość daleko od ‘wszystkie’ i dlatego ostatecznie nie jest tak różowo. Czołowe miejsce na liście “niewspółpracujących” zajmuje standardowa biblioteka C++. Z nią trzeba sobie poradzić we własnym zakresie.
Nie jest to aczkolwiek bardzo trudne, jako że każdy kompilator definiuje makro dla rozróżnienia wersji ANSI i Unicode. Visual C++ na przykład włącza w tym przypadku symbol _UNICODE, którego możemy użyć do stworzenia odpowiednich aliasów:

  1. namespace std
  2. {
  3.    #ifndef _UNICODE
  4.       // wersja ANSI
  5.       typedef string tstring;
  6.       #define cin tcin
  7.       #define cout tcout
  8.       typedef ostream tostream;
  9.       typedef istream tistream;
  10.       // ...
  11.    #else
  12.       // wersja Unicode
  13.       typedef wstring tstring;
  14.       // itd.
  15.    #endif
  16. }

Możemy je umieścić we własnym pliku nagłówkowym i dołączać we własnych projektach. Ponieważ jednak typów zależnych od znaków jest w STL całkiem sporo, można z powodzeniem użyć chociażby rozwiązania zamieszczonego na CodeProject, w razie potrzeby rozszerzając je o kolejne aliasy.

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

Najrzadsze słowo kluczowe

2007-12-16 22:33

Standardowy C++ ma ponad 50 podstawowych słów kluczowych. To naprawdę całkiem sporo, chociaż nowsze języki wykazują jeszcze większą tendencję do produkowania tych elementów składni. Te kilkadziesiąt na razie jednak wystarcza w zupełności. Oczywiście są sytuacje, w których dla przejrzystości przydałoby się dodatkowe słówko (jak na przykład coś podobnego abstract przy deklarowaniu metod czysto wirtualnych). Jeśli jednak nie jesteśmy specjalistami od C++, to może nam się przytrafić spotkanie z keywordem, którego nigdy wcześniej na oczy nie oglądaliśmy.

Dla mnie – i podejrzewam, że nie tylko dla mnie – ostatnim takim przypadkiem było ujrzenie tajemniczego słowa kluczowego mutable. Być może nie jest ono, jak tutaj sugeruję, najrzadziej używanym słowem kluczowym C++ w ogóle, ale jestem przekonany, że mieści się ono co najmniej w pierwsze trójce. A skoro już o nim wspominam, to wypadałoby wyjaśnić, do czego ono służy.
mutable jest mianowicie modyfikatorem, którym możemy opatrzyć pole klasy – na podobnej zasadzie jak np. static czy const (z którymi zresztą mutable wzajemnie się wyklucza). Pole posiadające taki atrybut może być następnie modyfikowane z poziomu każdej metody swojej klasy – włącznie z tymi metodami, które są zadeklarowane jako stałe (const).
Imponujące? Bynajmniej. Mówiąc mniej formalnie, mutable pozwala na to, by obiekt mógł być stały pod względem koncepcyjnym nawet jeśli z pewnych “technicznych” powodów musimy zmienić zawartość niektórych jego pól. C++ jest pedantyczny i traktuje każdą modyfikację wartości składowych obiektu jako niedozwoloną w metodzie stałej. Czasami jednak chcemy tak zrobić, jako że dla nas obiekt ten nadal będzie niezmieniony – mimo tego, iż ze ścisłego, bitowego punktu widzenia zostanie on zmodyfikowany.

Taka sytuacja może zajść na przykład wtedy, gdy pewne dane chcemy wyliczać na żądanie – wtedy, kiedy będą potrzebne. Żeby przykład był życiowy, weźmy element trójwymiarowej sceny, któremu przypiszemy macierz translacji, skalowania i obrotu. W pewnym momencie będziemy chcieli je przemnożyć i dostać macierz całej transformacji, ale nie opłaca się tego robić za każdym razem, gdy zmienia się któraś z macierzy składowych. Jednak metoda zwracająca nam iloczyn powinno zasadniczo być stała, co przeszkadza cacheowaniu tegoż iloczynu jako pola w obiekcie.
I tutaj z pomocą przychodzi nam mutable:

  1. class ISceneNode
  2. {
  3.    private:
  4.       MATRIX mtxTranslation, mtxRotaton, mtxScaling;   // macierze przekształceń
  5.       mutable MATRIX mtxTransform;    // złożenie powyższych
  6.       mutable bool bTransformValid;      // flaga okr., czy należy wyliczyć nowy iloczyn
  7.  
  8.    public:
  9.       // ustawianie przekształceń
  10.       void SetTranslation(const MATRIX& mtx)  
  11.          { mtxTranslation = mtx; bTransformValid = false; } // itd.
  12.  
  13.       // pobranie iloczynu przekształceń - metoda stała!
  14.       MATRIX GetTransform() const
  15.       {
  16.          // wyliczamy nowy iloczyn, jeśli stary jest nieaktualny
  17.          if (!bTransformValid)
  18.          {
  19.             mtxTransform = mtxTranslation * mtxRotation * mtxScaling;
  20.             bTransformValid = true;
  21.          }
  22.  
  23.          return mtxTransform;
  24.       }
  25. };

W ten sposób mamy za darmo elegancję, szybkość i spójność koncepcyjną. Znajomość egzotycznych elementów języka może więc wbrew pozorom przynosić czasem konkretne korzyści :)

PS. Jak widać, ten element jest na tyle egzotyczny, że nawet skrypt kolorujący składnię C++ nie potrafi sobie z nim poradzić ;-)

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

Standard i Visual C++ 2005

2007-12-14 17:40

Zainspirowany pewnym wątkiem na forum Warsztatu, traktującym o rzekomych poważnych niezgodnościach między standardem C++ a kompilatorem zawartym w MS Visual C++ 2005, postanowiłem nieco dogłębniej zbadać tę sprawę. Jak dotąd z owych niezgodności kojarzyłem tylko brak wsparcia dla szablonów eksportowanych – czyli “magicznego” słówka export, pozwalającego na rozdzielanie kodu szablonowego na pliki nagłówkowe i pliki .cpp tak samo, jak zwykłego. Nie jest to zresztą wielkie wykroczenie, jako że kompilatory wspierające szablony eksportowane można pewnie policzyć na palcach jednej ręki, a sam mechanizm też nie należy do szeroko znanych.

Co z innymi niezgodnościami? Odwołanie do dokumentacji Visual C++ przynosi ich jeszcze kilka. Z ciekawszych – oprócz braku obsługi słowa export, o którym już wspomniałem – mogę wymienić:

  • Ułomność preprocesorowego operatora # (tzw. łańcuchującego – stringizing operator), który zawiedzie, jeśli zastosujemy go względem stałej napisowej zawierającej sekwencje ucieczki (ciągi rozpoczynające się od znaku backslash: \n, \t, itd.).
  • Możliwość, że obiekty nie posiadające żadnych pól będą dzieliły miejsce w pamięci (którego i tak nie zajmują) z innymi obiektami. Standard C++ wymaga natomiast, żeby każdy obiekt miał unikalną lokalizację w pamięci operacyjnej w trakcie całego swojego istnienia (obiektu, nie standardu C++ ;]).
  • Brak wykorzystania specyfikacji wyjątków (instrukcji throw (typ_wyjątku)). Innymi słowy, w Visual C++ nie sprawdza się, czy funkcja wyrzuciła wyjątek takiego samego typu, który wcześniej został w niej zadeklarowany.
  • Pewne ograniczenia liczbowe, jak choćby: liczbę poziomów zagnieżdżenia bloków kodu (VC++ obsługuje 64, standard rekomenduje 256), kwalifikatorów dostępu dla jednej nazwy (127 zamiast 256) czy parametrów szablonu (64 przeciwko 1024).

I to mniej więcej tyle. Być może kogoś zmartwi brak możliwości napisania algorytmu ze 100 zagnieżdżonymi pętlami lub szablonu z tyloma parametrami, ale osobiście mam co do tego pewne wątpliwości :)

Funkcje debugujące

2007-12-01 13:29

Kiedy już przekonamy się o korzyściach płynących z regularnego stosowania debuggera (a każdy początkujący programista powinien przekonać się o nich jak najszybciej), być może zaczniemy się zastanawiać, jakaż to “magia” sprawia, że cały ten proces w ogóle jest możliwy. W jaki sposób program śledzący potrafi dostać się do wnętrza naszej aplikacji i wylistować zawartość wszystkich zmiennych, nie mówiąc już o możliwości ustawiania punktów przerwań (breakpoints) czy pracy krokowej. Czy debuggery korzystają z jakichś nieudokumentowanych “wytrychów” do samego jądra systemu operacyjnego?…

Otóż niekoniecznie. Na przykład Windows API udostępnia zwyczajne funkcje systemowe, za pomocą których jeden proces może śledzić zachowanie innego. Obejmuje to przyłączenie się do innego procesu w charakterze debuggera, obsługę przeróżnych interesujących zdarzeń (jak np. załadowanie biblioteki DLL lub stworzenie nowego wątku przez proces, który śledzimy), a także odczytywanie zawartości pamięci procesu (jeśli mamy do tego uprawnienia) lub kontekstu jego wątku.
Naturalnie mało kto będzie pisał swój własny debugger, więc wspomniane funkcje są nieszczególnie przydatne dla większości programistów. Jednak istnieje też kilka takich, które można użytecznie stosować po drugiej stronie – w aplikacji, która jest debugowana:

  • OutputDebugString to prawdopodobnie ta najbardziej znana. Służy ona do wysyłania komunikatów do debuggera, które zwykle są wyświetlane w specjalnym okienku naszego IDE. Zazwyczaj jest to bardziej poręczne rozwiązanie niż chociażby pokazywanie okienek komunikatów, które trzeba przecież potwierdzać kliknięciami w OK. Funkcję tę można aczkolwiek zastąpić wypisywaniem na standardowe wyjście diagnostyczne (stderr, reprezentowane w iostream przez obiekty cerr lub wcerr).
  • DebugBreak działa z kolei jak punkt przerwania. Zasadniczo funkcja ta wywołuje odpowiedni wyjątek systemowy (nie mający za wiele wspólnego z wyjątkami języka C++), który każdy porządny debugger złapie. W środowisku programistycznym rezultatem będzie zawieszenie działania programu i ustawienie się z widokiem kodu źródłowego na miejsce wywołania funkcji, co oczywiście znakomicie ułatwia odnalezienie przyczyny błędu. Jeżeli jednak nikt nie śledzi naszej aplikacji, wówczas rzucony wyjątek spowoduje jej awaryjne zakończenie. Generalnie podobny efekt co DebugBreak powinno dać wywołanie przerwania numer 3 (czyli asm { int 3 }).
  • IsDebuggerPresent pozwala z kolei określić, czy aplikacja jest aktualnie debugowana przez jakiś inny proces. Dzięki temu możemy na przykład przekazywać do komunikatów diagnostycznych większą liczbę informacji, wiedząc, że ktoś “po drugiej stronie” faktycznie je odczytuje :)

Wprawdzie samo korzystanie z tych funkcji nie zastąpi dobrego systemu kontroli błędów i ich raportowania (np. zapisywania w dzienniku), lecz z pewnością może pomóc. Bardzo dobrze prezentuje się na przykład okienko z obszernym komunikatem o błędzie i trzema możliwościami decyzji: kontynuacji programu, przejścia do trybu debugowania albo awaryjnego zakończenia aplikacji. Przy takim rozwiązaniu aż chce się popełniać błędy ;)

Tags: , ,
Author: Xion, posted under Programming » Comments Off on Funkcje debugujące

Typy rozróżnialne

2007-11-28 10:11

W większości języków możemy zdefiniować nową nazwę dla istniejącego typu danych; nazywa się ją zwykle aliasem. I tak na przykład w C/C++ jest to możliwe za pomocą słowa kluczowego typedef. Analogicznie w Delphi mamy od tego słowo kluczowe type:
[delphi]type TMyInt = Integer;[/delphi]
Tak powstały typ TMyInt jest faktycznie tylko aliasem. Zmienne należące do tego typu są bowiem całkowicie kompatybilne ze zmiennymi zwykłego typu całkowitego Integer. W razie potrzeby konwersja między nimi może bez problemu zachodzić w obie strony.

Jeżeli jednak użylibyśmy deklaracji w formie:
[delphi]type TMyInt = type Integer;[/delphi]
wówczas TMyInt byłby już zupełnie innym typem niż Integer. Mimo że oba mogłyby przechowywać wartości tego samego rodzaju (liczby całkowite) i z tego samego zakresu, konwersje między nimi wymagałyby rzutowania.
Można by sądzić, że tworzenie takich typów rozróżnialnych “na siłę” jest bezcelowe. Zauważmy jednak, że typy wyliczeniowe (deklarowane przez enum) są przecież także w gruncie rzeczy liczbami z określonego zbioru. Najczęściej jednak nie chcemy, aby możliwa była niejawna konwersja między nimi a normalnymi typami liczbami. Wszystko dlatego, że w enumach liczby nie pełnią funkcji liczb, tylko identyfikatorów pewnych stanów.

Podobnie tutaj w przypadku TMyInt nie chodzi nam zapewne o liczby w sensie ich wartości, tylko o coś w rodzaju uchwytów – identyfikatorów obiektow. Kopalnią typów przeznaczonych do takiego właśnie celu jest oczywiście Windows API, zawierające tak znane i lubiane typy jak HWND, HINSTANCE, HDC, itd. Wszystkie one są w gruncie rzeczy liczbami 32-bitowymi, a mimo to nie są ze sobą kompatybilne. Gdyby API to było obiektowe, obiekty reprezentowane obecnie przez te uchwyty należałyby do różnych klas.
Efekt niezgodności uchwytów osiągnięto, deklarując ich typy nie jako aliasy na DWORD:

  1. typedef DWORD HWND;  // wszystkie tak określone typy będą ze sobą zgodne

lecz jako niezwiązane ze sobą typy wskaźnikowe:

  1. struct __HWND;
  2. typedef __HWND* HWND;

Można to uznać za dość pokrętną sztuczkę, ale na pewno jest ona lepsza niż tworzenie typu wyliczeniowego zawierającego nieco ponad 4 miliardy (232) nazwanych stałych ;]

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


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