Podręcznik

3. Koncepty

3.1. Obiektowość a szablony

Programowanie uogólnione i obiektowe to dwa różne podejścia. Samo programowanie uogólnione niekoniecznie jest silnie obiektowe.

W programowaniu obiektowym kluczowym jest projektowanie klas i ich wzajemnych relacji, włączając w to kluczową z nich - dziedziczenie, które w połączeniu z polimorfizmem daje nam naprawdę wielkie możliwości. Ale - w życiu zazwyczaj warto wystrzegać się różnych ekstremizmów, w tym także i tych związanych z nadmiernym stosowaniem jakiejś techniki w połączeniu z bezgranicznym zaufaniem co do jej doskonałości. Programowanie obiektowe kładzie nacisk na struktury danych reprezentujące obiekty w rzeczywistym świecie, a operacje są związane z tymi obiektami, ale nie jest jedynym podejściem.

Wystrzegajcie się postrzegania problemów przez filtr narzędzi które macie do dyspozycji. Jeśli jedynym waszym narzędziem jest młotek - to każdy problem będzie wyglądał jak gwóźdź...

Rozważmy przykład wcześniej wspomnianego polimorfizmu. Omówiliśmy już jedną jego odmianę - polimorfizm dynamiczny.

Polimorfizm dynamiczny to zdolność programu do decydowania o tym, która funkcja zostanie wywołana pod daną nazwą nie w momencie kompilacji, ale w trakcie działania programu. W obiektowości, polimorfizm dynamiczny jest często realizowany poprzez wykorzystanie wskaźników i referencji do klas bazowych oraz stosowania mechanizmu wirtualnych funkcji.

Opierając się na tożsamości obiektu, oraz - na jego klasie i hierarchii klas do których należy - podejmowana jest decyzja odnośnie postaci metody którą należy wywołać. Ale może nie zawsze jest to najlepsze rozwiązanie?

Polimorfizm statyczny

Może nie warto zastanawiać się, jakiego typu jest obiekt w trakcie wykonania programu, tylko w trakcie jego kompilacji sprawdzić, czy obiekt dla którego chcemy jakąś metodę wywołać - ma tą metodę? I to jest właśnie clou polimorfizmu statycznego.

Polimorfizm statyczny daje nam możliwość zrezygnowania z wymuszania typu argumentu podczas wywoływania funkcji. Szablony funkcji w C++ pozwalają na tworzenie funkcji, które działają na różnych typach danych, pod warunkiem, że te typy spełniają określone wymagania.

Przykładowo, funkcja draw_template pozwala na rysowanie obiektów przechowywanych w tablicy dla różnych typów danych, o ile posiadają one metodę `draw`.


template<typename T> void draw_template(T table[], size_t n) {
    for(size_t i = 0; i < n; ++i)
        table[i].draw();
}

Jaka jest postać metody draw? Pisząc funkcję - nie wiemy. Ale wiemy że zostanie wykonana - więc uzyskaliśmy "zmiennopostaciowość" - tyle że innymi środkami

Polimorfizm dynamiczny umożliwia operowanie na zbiorze obiektów o różnych typach, korzystając z mechanizmu dziedziczenia. Kluczową koncepcją jest wspólna hierarchia dziedziczenia, co oznacza, że obiekty muszą pochodzić z tych samych klas bazowych. Wymaga to użycia wskaźników lub referencji do obiektów oraz zastosowania funkcji wirtualnych. Pomimo tego, że generuje większy kod, pozwala na elastyczne korzystanie z różnych typów obiektów w czasie wykonania programu.

W przeciwieństwie do polimorfizmu dynamicznego, szablony implementują polimorfizm statyczny. Działają one na jednorodnych zbiorach obiektów, co oznacza, że obiekty nie muszą pochodzić z tej samej hierarchii dziedziczenia. Szablony umożliwiają operowanie na różnych typach obiektów bez konieczności korzystania ze wskaźników, referencji czy funkcji wirtualnych. W efekcie otrzymanego kodu jest zazwyczaj mniejszy i może działać szybciej, co stanowi istotną korzyść w kontekście optymalizacji wydajnościowej.

Podsumowując, oba rodzaje polimorfizmu są potężnymi narzędziami, ale wybór między nimi zależy od konkretnych potrzeb i charakteru programu. Polimorfizm dynamiczny jest przydatny, gdy mamy do czynienia z niejednorodnymi zbiorami obiektów, natomiast polimorfizm statyczny w kontekście szablonów pozwala na efektywną pracę z jednorodnymi zbiorami, co może przekładać się na lepszą wydajność kodu.