Załóżmy, że chcesz napisać funkcję obliczającą maksimum dwóch liczb. Można to zrobić w następujący sposób:
int max(int x, int y)
{
return (x < y) ? y : x;
// Note: we use < instead of > because std::max uses <
}Chociaż obiekt wywołujący może przekazywać do funkcji różne wartości, typ parametrów jest stały, więc obiekt wywołujący może przekazywać tylko int wartości. Oznacza to, że ta funkcja naprawdę działa dobrze tylko w przypadku liczb całkowitych (i typów, które można promować do int).
Co się więc stanie później, gdy chcesz znaleźć maksymalnie dwie double wartości? Ponieważ C++ wymaga od nas określenia typu wszystkich parametrów funkcji, rozwiązaniem jest utworzenie nowej przeciążonej wersji max z parametrami typu double:
double max(double x, double y)
{
return (x < y) ? y: x;
}Zauważ, że kod implementujący wersję podwójną of max jest dokładnie taki sam jak w wersji int max! W rzeczywistości ta implementacja działa dla wielu różnych typów: w tym int, double, long, long double, a nawet dla nowych typów, które sam stworzyłeś (co omówimy w przyszłych lekcjach).
Konieczność tworzenia przeciążonych funkcji z tą samą implementacją dla każdego zestawu typów parametrów, które chcemy obsługiwać, jest problemem związanym z konserwacją, receptą na błędy i wyraźne naruszenie zasady DRY (nie powtarzaj się) Tu pojawia się także mniej oczywiste wyzwanie: programista chcący skorzystać z funkcji max może chcieć wywołać ją z typem argumentu, którego autor max nie przewidział (i dlatego nie napisał dla niej przeciążonej funkcji).
To, czego nam naprawdę brakuje, to jakiś sposób na napisanie pojedynczej wersji. of max , które mogą współpracować z argumentami dowolnego typu (nawet typami, które mogły nie zostać przewidziane podczas pisania kodu dla max ). Normalne funkcje po prostu nie spełniają tutaj zadania. Na szczęście C++ obsługuje inną funkcję zaprojektowaną specjalnie w celu rozwiązania tego rodzaju problemów.
Witamy w świecie szablonów C++.
Wprowadzenie do C++. szablony
W C++ system szablonów został zaprojektowany w celu uproszczenia procesu tworzenia funkcji (lub klas), które mogą pracować z różnymi typami danych.
Zamiast ręcznie tworzyć kilka w większości identycznych funkcji lub klas (po jednej dla każdego zestawu różnych typów), zamiast tego tworzymy pojedynczą template. Podobnie jak normalna definicja, template definicja opisuje, jak wygląda funkcja lub klasa w przeciwieństwie do normalna definicja (gdzie muszą być określone wszystkie typy), w szablonie możemy użyć jednego lub więcej typów symboli zastępczych. Typ zastępczy reprezentuje jakiś typ, który nie jest znany w momencie definiowania szablonu, ale zostanie on podany później (kiedy szablon będzie używany).
Po zdefiniowaniu szablonu kompilator może użyć szablonu do wygenerowania dowolnej liczby przeciążonych funkcji (lub klas), z których każda używa innego rzeczywistego typu!
Efekt końcowy jest taki sam - kończy się na tym. z grupą w większości identycznych funkcji lub klas (po jednej dla każdego zestawu różnych typów), ale musimy utworzyć i obsługiwać tylko jeden szablon, a kompilator wykonuje całą ciężką pracę, aby utworzyć za nas resztę.
Kluczowa informacja
Kompilator może użyć jednego szablonu do wygenerowania rodziny powiązanych funkcji lub klas, z których każda używa innego zestawu rzeczywistych typów.
Na marginesie…
Ponieważ koncepcję szablonów może trudno opisać słowami, spróbujmy analogia.
Gdybyś sprawdził w słowniku słowo „szablon”, znalazłbyś definicję podobną do poniższej: „szablon to model, który służy jako wzór do tworzenia podobnych obiektów”. Jednym z typów szablonów, który jest bardzo łatwy do zrozumienia, jest szablon. Szablon to cienki kawałek materiału (np. kartonu lub plastiku) z wyciętym kształtem (np. wesołą buźką). Umieszczając szablon na innym przedmiocie, a następnie spryskując otwór farbą, można bardzo szybko odtworzyć wycięty kształt. Sam szablon wystarczy utworzyć tylko raz, a następnie można go używać wielokrotnie, aż do uzyskania wyciętego kształtu w dowolnej liczbie kolorów. Co więcej, nie trzeba określać koloru kształtu utworzonego za pomocą szablonu, dopóki szablon nie zostanie faktycznie użyty.
Szablon to zasadniczo szablon do tworzenia funkcji lub klas. Tworzymy szablon (nasz szablon) raz, a następnie możemy go używać tyle razy, ile potrzeba, aby szablonować funkcję lub klasę dla określonego zestawu rzeczywistych typów. Rzeczywiste typy nie muszą być określane do czasu faktycznego użycia szablonu.
Ponieważ rzeczywiste typy nie są określane do czasu użycia szablonu w programie (nie podczas pisania szablonu), autor szablonu nie musi próbować przewidzieć wszystkich rzeczywistych typów, które mogą zostać użyte. Oznacza to, że kodu szablonu można używać z typami, które nawet nie istniały w momencie pisania szablonu! Zobaczymy, jak to się przyda później, gdy zaczniemy eksplorować standardową bibliotekę C++, która jest absolutnie pełna kodu szablonu!
Kluczowa informacja
Szablony mogą współpracować z typami, które nawet nie istniały, gdy szablon był pisany. Pomaga to uczynić kod szablonów zarówno elastycznym, jak i przyszłościowym!
W pozostałej części tej lekcji przedstawimy i zbadamy, jak tworzyć szablony funkcji oraz opiszemy bardziej szczegółowo, jak działają. Omówienie szablonów klas zachowamy do czasu, aż omówimy, czym są klasy.
Szablony funkcji
A szablonu funkcji to definicja podobna do funkcji, która służy do generowania jednej lub większej liczby przeciążonych funkcji, każda z innym zestawem rzeczywistych typów. To właśnie pozwoli nam stworzyć funkcje, które mogą współpracować z wieloma różnymi typami. Początkowy szablon funkcji używany do generowania innych funkcji nazywany jest szablonem podstawowym, a funkcje generowane z szablonu podstawowego nazywane są funkcjami instancyjnymi.
Kiedy tworzymy podstawowy szablon funkcji, używamy typów zastępczych (technicznie nazywanych parametrów szablonu typu, nieformalnie nazywanych szablonem typy) dla dowolnych typów parametrów, typów zwracanych lub typów używanych w treści funkcji, które chcemy określić później przez użytkownika szablonu.
Dla zaawansowanych czytelników
C++ obsługuje 3 różne rodzaje parametrów szablonu:
- Parametry szablonu typu (gdzie parametr szablonu reprezentuje typ).
- Parametry szablonu nietypowe (gdzie parametr szablonu reprezentuje constexpr wartość).
- Parametry szablonu szablonu (gdzie parametr szablonu reprezentuje szablon).
Parametry szablonu typu są zdecydowanie najczęstsze, więc skupimy się na nich w pierwszej kolejności. Omówimy także parametry szablonów nietypowych, które są coraz częściej stosowane we współczesnym C++.
Szablony funkcji najlepiej uczyć się na przykładach, dlatego przekonwertujmy naszą normalną max(int, int) funkcję z powyższego przykładu na szablon funkcji. Jest to zaskakująco łatwe i po drodze wyjaśnimy, co się dzieje.
Tworzenie max() szablonu funkcji
Oto int wersji max() ponownie:
int max(int x, int y)
{
return (x < y) ? y : x;
}Zauważ, że w tej funkcji używamy typu int trzy razy: raz dla parametru x, raz dla parametr y i raz dla zwracanego typu funkcji.
Aby utworzyć szablon funkcji dla max(), zrobimy dwie rzeczy. Najpierw zastąpimy wszystkie typy, które chcemy później określić, parametrami szablonu typów. W tym przypadku, ponieważ mamy tylko jeden typ do zastąpienia (int), potrzebujemy tylko jednego parametru szablonu typu (który wywołamy T):
Oto nasza nowa funkcja, która używa pojedynczego typu szablonu, gdzie wszystkie wystąpienia rzeczywistego typu int zostały zastąpione parametrem szablonu typu T :
T max(T x, T y) // won't compile because we haven't defined T
{
return (x < y) ? y : x;
}To dobry początek - jednak nie zostanie skompilowany, ponieważ kompilator tego nie wie czym T jest! I to jest nadal normalna funkcja, a nie szablon funkcji.
Po drugie, powiemy kompilatorowi, że jest to szablon i że T jest parametrem szablonu typu, który jest symbolem zastępczym dowolnego typu. Obydwa są wykonywane przy użyciu deklaracją parametrów szablonu, który definiuje dowolne parametry szablonu, które będą później używane (lub szablon klasy) poniżej. Dlatego każdy szablon funkcji lub szablon klasy wymaga własnej deklaracji parametrów szablonu.
template <typename T> // this is the template parameter declaration defining T as a type template parameter
T max(T x, T y) // this is the function template definition for max<T>
{
return (x < y) ? y : x;
}W naszej deklaracji parametrów szablonu zaczynamy od słowa kluczowego template, które informuje kompilator, że tworzymy szablon. Następnie określamy wszystkie parametry szablonu, których będzie używał nasz szablon, ujęte w nawiasy ostre (<>. Dla każdego parametru szablonu typu używamy słowa kluczowego typename (preferowany) lub class, po którym następuje nazwa parametru szablonu typu (np. T).
Powiązana treść
Jak tworzyć szablony funkcji z wieloma typami szablonów omawiamy na lekcji 11.8 -- Szablony funkcji z wieloma typami szablonów.
Na marginesie…
W tym kontekście nie ma różnicy pomiędzy słowami kluczowymi typename i class . Często można spotkać ludzi używających słowa kluczowego class od czasu jego wcześniejszego wprowadzenia do języka. Jednak my wolimy nowsze typename słowo kluczowe, ponieważ jest ono bardziej przejrzyste że parametr szablonu typu można zastąpić dowolnym typem (np. typem podstawowym), a nie tylko typami klasowymi.
Wierz lub nie, ale skończyliśmy! Stworzyliśmy szablonową wersję naszej max() funkcji, która może przyjmować argumenty różnych typów.
W następnej lekcji przyjrzymy się, jak używamy naszego szablonu funkcji max do generowania jednego lub more max() funkcje z parametrami różnych typów i faktycznie wywołują te funkcje.
Nazewnictwo parametrów szablonu
Podobnie jak często używamy jednej litery dla nazw zmiennych używanych w trywialnych sytuacjach (np. x), zwyczajowo używa się pojedynczej dużej litery (zaczynając od T), gdy parametr szablonu jest używany w trywialny lub oczywisty sposób. Na przykład w naszym max szablon:
template <typename T>
T max(T x, T y)
{
return (x < y) ? y : x;
}nie musimy podawać T złożonej nazwy, ponieważ jest to oczywiście tylko typ zastępczy dla porównywanych wartości i T może to być dowolny typ, który można porównać (taki jak int, double lub char, ale nie nullptr).
Nasze szablony funkcji będą generalnie używać tej konwencji nazewnictwa.
Jeśli parametr szablonu typu ma nieoczywiste zastosowanie lub specyficzne wymagania, które muszą być spełnione, istnieją dwie powszechne konwencje dla takich nazw:
- Zaczynanie od dużej litery (np.
Allocator). Biblioteka standardowa stosuje tę konwencję nazewnictwa. - Przedrostek a
T, a następnie rozpoczynanie wielką literą (np.TAllocator). type to parametr szablonu typu.
To, który wybierzesz, zależy od osobistych preferencji.
Dla zaawansowanych czytelników
Na przykład biblioteka standardowa ma przeciążenie std::max() zadeklarowane w ten sposób:
template< class T, class Compare >
const T& max( const T& a, const T& b, Compare comp ); // ignore the & for now, we'll cover these in a future lessonPonieważ a i b są typu T, wiemy, że nie obchodzi nas, jakiego typu a i b są - mogą być dowolnego typu. Ponieważ comp ma typ Compare wiemy, że comp musi to być typ spełniający wymagania dla Compare (cokolwiek to jest).
Gdy tworzona jest instancja szablonu funkcji, kompilator zastępuje parametry szablonu argumentami szablonu, a następnie kompiluje powstałą instancję funkcji. To, czy funkcja się kompiluje, zależy od tego, w jaki sposób obiekty każdego typu są używane w funkcji. Dlatego też wymagania dotyczące danego parametru szablonu są zasadniczo zdefiniowane w sposób dorozumiany.
Ponieważ wywnioskowanie wymagań na podstawie sposobu użytkowania obiektów tego typu może być trudne, jest to jeden z tych obszarów, w których przydatne jest zapoznanie się z dokumentacją techniczną, która powinna wyraźnie określać wymagania. Na przykład, jeśli chcemy wiedzieć, jakie są wymagania dla Compare , możemy zajrzeć do dokumentacji dla std::max (np. zobacz https://en.cppreference.com/w/cpp/algorithm/max) i powinna być tam wymieniona.
Najlepsza praktyka
Użyj pojedynczej dużej litery zaczynającej się od T (np. T, U, V, itp.), aby nazwać parametry szablonu typów, które są używane w trywialny lub oczywisty sposób i reprezentują „dowolny rozsądny typ”.
Jeśli parametr szablonu typu ma nieoczywiste zastosowanie lub specyficzne wymagania, które należy spełnić, uzasadniona jest bardziej opisowa nazwa (np. Allocator lub TAllocator).
Czas quizu
Pytanie nr 1
Opisz, dlaczego plany budowlane są rodzajem szablonu.

