Długo się zastanawiałem, czy umieścić ten rozdział. Doszedłem do wniosku, że napiszę kilka zdań, aby przynajmniej przybliżyć to zagadnienie. Szablony to kolejna z nowości, jaka została wprowadzona do C++ całkiem niedawno. Szablony mogą czasami zaoszczędzić Ci mnóstwo pracy. Szablon funkcji to pewien mechanizm umożliwiający automatyczną generację funkcji :-/ Jest to schemat, według którego postępuje kompilator. My dostarczamy jedynie ogólny zarys ciała funkcji bez określania konkretnych argumentów wywołania. Na tej podstawie kompilator jest w stanie wytworzyć dowolną ilość funkcji działających identycznie, a różniących się jedynie typem argumentów funkcji. Dziwne, co? Aby było to bardziej zrozumiałe posłużę się tutaj analogią z życia. Będąc w szkole na pewno zetknąłeś się z krzywkami, czy też z krzywikami. Jeżeli nie to wyjaśnię czym one są. Krzywki to przybory kreślarskie służące do rysowania różnych zawijasów. Wystarczy odpowiednio przyłożyć owe ustrojstwo do kartki i nakreślić dowolną linię. Bez krzywek narysowanie takiej linii wprawdzie jest możliwe, ale potrzeba na to więcej czasu. Podobnie wygląda sprawa w przypadku szablonów funkcji. Taki szablon jest właśnie taką krzywką, a my informujemy tylko kompilator jaką kreskę chcemy nakreślić. Pora na mały przykład:
1 2 3 4 5 6 7 |
int sumuj(int liczba1, int liczba2) { return (liczba1 + liczba2); } |
Tutaj raczej nie powinno być problemów. Jest to najzwyklejsza funkcja sumująca dwie liczby typu int. Co w sytuacji, gdy podstawimy do niej liczby float? Okazuje się, że błędu kompilacji nie będzie. Kompilator jedynie ostrzeże nas. Czy wiesz może, dlaczego tak się stanie? Zastanówmy się co tak naprawdę zaszło. Wywołaliśmy funkcję sumuj przyjmującą dwie liczby całkowite typu int. Zamiast liczb całkowitych zostały użyte liczby zmiennoprzecinkowe typu float. Kompilator widząc to stara się pójść nam na rękę i mimo wszystko skompilować program. Postanawia tuż przed wywołaniem funkcji zamienić liczby typu float na typ int. Jest to możliwe. Jest to niejawna konwersja typów. W wyniku tego liczby posiadające część ułamkową zostaną obcięte i zamienione na liczby całkowite. Czasem to nam nie przeszkadza. Jednak gdy liczy się dokładność nie możemy sobie na to pozwolić. Co więc robić? Wystarczy zdefiniować funkcję pobierającą dwie liczby typu float i po sprawie. A co w sytuacji, gdy sumować będziemy liczbę typu float i int? Znowu trzeba pisać nową definicję mogącą obsłużyć takie typy. Niby nie problem. Wystarczy skopiować te cztery linijki kodu. Jednak po co tracić czas na takie kombinacje, skoro można wykonać to znacznie szybciej i lepiej. Najwygodniej skorzystać tutaj z szablonu funkcji.
1 2 3 4 5 6 7 8 9 |
template double sumuj(parametr1 argument1, parametr2 argument2) { return (argument1 + argument2); } |
A cóż to jest? To jest właśnie szablon funkcji 🙂 Pojawia się tutaj sporo nowości. Pierwszą z nich jest słówko template. Znaczy ono po prostu szablon. Następnie w nawiasach trójkątnych określane są parametry szablonu. Zwróć uwagę na składnię. Pojawia się tam słówko class. Dosłownie oznacza ono klasę, czyli typ parametru. Za nim widnieje nazwa parametru szablonu. Później jest jakby definicja funkcji. Tutaj również zwróć uwagę na formę zapisu. W przypadku zwykłej funkcji zamiast słów parametr1 i parametr2 wystąpiłyby nazwy typów, czyli int, float, long itp. Teraz są nazwy parametrów szablonu. Warto tutaj zwrócić uwagę na fakt, iż definicja szablonu funkcji nie oznacza definicji samej funkcji. Tak naprawdę definicja funkcji powstanie dopiero w momencie jej wywołania. Wtedy kompilator sprawdza sobie typy argumentów i tworzy odpowiednią funkcję. Zatem zapis sumuj(11, 33.43); spowoduje wykreowanie następującej funkcji:
1 2 3 4 5 6 7 |
double sumuj(int argument1, float argument2) { return (argument1 + argument2); } |
W wywołaniu funkcji możesz umieszczać prawie dowolne typy. Wiesz może, dlaczego nie wszystko można umieścić w wywołaniu funkcji szablonowej? Zastanów się, co by się stało, gdybyś postawił tam wskaźniki do tablic znakowych. Wówczas kompilator wygenerowałby taką funkcję:
1 2 3 4 5 6 7 |
double sumuj(char * argument1, char * argument2) { return (argument1+ argument2); } |
Co ma oznaczać zapis: argument1 + argument2, skoro oba argumenty są wskaźnikami do typu char? Jest to raczej bezsensu. Kompilator traktuje to, jako błąd i nie skompiluje tego. Sam widzisz, że nie wszystko może być parametrem funkcji szablonowej. Jeżeli uparcie chciałbyś mieć taką funkcję to należy zastosować funkcję specjalizowaną.
Funkcje specjalizowane
Funkcja specjalizowana to funkcja będąca rozwinięciem szablonu funkcji. Definiując taką funkcję informujemy kompilator, że w momencie napotkania takiego, a takiego wywołania funkcji nie wystarczy bezmyślnie skorzystać z dostarczonego szablonu funkcji, ale z funkcji specjalizowanej. Nasz dotychczasowy szablon funkcji wyglądał tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
template double sumuj(parametr1 argument1, parametr2 argument2) { return (argument1 + argument2); } double sumuj(char *argument1, char *argument2)//funkcja specjalizowana { return (*argument1 + *argument2); } |
Teraz dodaliśmy do niego funkcję specjalizowaną. Zwróć uwagę, że przy definicji funkcji specjalizowanej już nie podajemy słówka template. Jest to jakby zwykła definicja funkcji. Podczas definiowania funkcji specjalizowanych należy pamiętać o właściwym rozmieszczeniu ich w pliku. Najpierw umieszczasz szablon funkcji, a później funkcję specjalizowaną. Dopiero teraz możesz korzystać z tych funkcji, czyli je wywoływać. Jest to bardzo ważne. Popatrz, co by się stało, gdybyśmy pomiędzy definicją szablonu funkcji, a definicją funkcji specjalizowanej umieścili wywołanie funkcji z argumentami będącymi wskaźnikami do typu char. Kompilator przygląda się jej dokładnie i analizuje. Zauważa, że dostarczyliśmy mu szablon, z którego jest w stanie wygenerować taką funkcję, więc robi to. Teraz natrafia na funkcję specjalizowaną. To właśnie ona miała zostać użyta w powyższym wywołaniu. Jednak przez nasze przeoczenie stało się inaczej. Krótko mówiąc jest tak, jakby funkcja specjalizowana nigdy nie istniała. Sami jesteśmy sobie winni.
Autor: Kesay
[Artykuł pochodzi ze strony guidecpp.prv.pl, autor wyraził zgodę na publikację]