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
:
lecz jako niezwiązane ze sobą typy wskaźnikowe:
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 ;]
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:
Nieco bardziej życiowy przypadek to zdefiniowanie własnego alokatora STL i chęć użycia go w standardowych kontenerach:
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:
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:
która sprawi, że np. my_list
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.