Wątek przeniesiony 2022-08-21 22:19 z Inżynieria oprogramowania przez Riddle.

Programowanie generyczne (template) - a komu to potrzebne? a na co?

1

Zacząłem poznawać programowanie generyczne z użyciem template w c++

słuchają wywiadu z Bjerne Strustrupem natknąłem się na Aleksandra Stefanowa autora biblioteki do programowania generycznego w c++
http://stepanovpapers.com/

Sztandarowym przykładem użycia programowania generycznego jest wektor w c++, jednak dlaczego lepiej tworzyć template klasy a nie obiekt ze zmiennym zachowaniem? dlaczego używać programowania generycznego a kiedy go nie stosować?

Jakie są przykłady w których można użyć go i dobrze się sprawdzi? Macie jakieś historie gdzie użyliście i faktycznie dobrze działało?

5

Wczuwam się ... a nie chodzi o to, aby mieć (wiele/wszyskie) zalety np dziedziczenia/wzorćów obiektowych, przy C++ kanonie "zerowy narzut kosztów na runtime" ???

8

"Obiekt ze zmiennym zachowaniem" to drabinka ifów, co prowadzi do architektury spaghetti, co nie jest pożądanym efektem w programowaniu obiektowym, ani żadnym innym...

Użycie template'ów może mieć uzasadnienie w zależności od obranej drogi.
Ja np. kiedyś zrobiłem sklepik w grze, a że miałem itemy różnego typu, to mogłem z takiego sklepiku zrobić template i każdy typ itemu miał swój sklepik.

8

Nie da się (z tego co wiem) w C++ zrobić sensownego wektora który będzie trzymać obiekty/struktury, bo wielkość obiektu/struktury jest zmienna. Jakby chcieć zrobić bezsensowny (czyli taki jak był w Javie 4) to działałoby to tylko z wskaźnikami na obiekty/struktury (i może z referencjami). Czyli dokładnie tak jak działało w Javie 4, bo w Javie wszystkie obiekty są dostępne pośrednio przez jedna referencje

Oczywiście mogę się mylić i możesz próbować tworzyć sensowna implementacje wektora bez użycia generykow/szablonów. Bardzo chętnie bym zobaczył taką implementacje

3

@KamilAdam:

To będzie (być może) pochodna czegoś tez innego, w czystym C++ nie ma pra-obiektu (jest w dialektach, ale wtedy obejnuje wyłącznie klasy frameworku).
istnienie takiego pra-obiektu by to i owo ułatwiło

0
ZrobieDobrze napisał(a):

@KamilAdam:

To będzie (być może) pochodna czegoś tez innego, w czystym C++ nie ma pra-obiektu

Ale jest znienawidzony void *. Object w Javie rózni się w zasadzie tylko tym że masz jesz metodę toString (i parę nieużytecznych metod do synchronizacji wątków). Przy wyciąganiu elementów z kolekcji bez generyków (jak w Javie 4) i tak trzeba robić rzutowania

4

@KamilAdam:

ale jest znienawidzony void*

void* to nie C++. W C++ nie ma potrzeby używania go do niczego, jeśli nie robimy czegoś z jakąś biblioteką w C. Nazywanie void* odpowiednikiem praobiektu z innych języków w C++ to nadużycie.

2
several napisał(a):

Nazywanie void* odpowiednikiem praobiektu z innych języków w C++ to nadużycie.

Oczywiście nie uważam void* za przedka wszystkich obiektów. Bardziej chciałem użyć tego feature'a że wszystkie wskaźniki można zrzutować na void* i z powrotem. Moim celem jest tu tylko pokazanie że kolekcje (np wektor w C++) byłyby bardzo prymitywne bez generyków/szablonów.

W Javie 4 używanie kollekcji wyglądało mniej więcej tak


List list = new ArrayList();             //w zasadzie kolekcja Object
list.add("SomeString");                  // automatyczna konwersja String na Object
String someString = (String)list.get(0); //trzeba ręcznie *odzyskać* typ

w C++ przy czymś takim potrzebne by były pewnie dwie konwersje

13
Marcin Marcin napisał(a):

Jakie są przykłady w których można użyć go i dobrze się sprawdzi? Macie jakieś historie gdzie użyliście i faktycznie dobrze działało?

Dla mnie to pytanie w stylu: użyliście kiedyś funkcji i faktycznie dobrze działało ?

Generyki, czy tam templaty to podstawa polimorfizmu (w ogólności, a nie tego jednego rodzaju poliformizmu (Subtyping), który jest tłuczony na OOP) - czyli pisania kodu, tak żeby dało się wielokrotnie użyć - dla różnych typów danych. W języku ze statycznymi typami trudno bez tego żyć.

Faktycznie, to co można zrobić w templatach da się zwykle zrobić alternatywnie przy pomocy subtype, ale zwykle tracimy wtedy dokładne typy i skazujemy się na downcasting - strasznie smutne.

0

Dla mnie to pytanie w stylu: użyliście kiedyś funkcji i faktycznie dobrze działało ?

Szczerze, przyznam się bez bicia, że ciężko mi wymyślić inne miejsce dla szablonów poza kontenerami typu vector. Pewnie za dużo CRUDzenia mam na co dzień, stąd nie przychodzi mi do głowy coś innego, więc z chęcią podbiję to pytanie - gdzie zastosowaliście szablony, że dobrze to zadziałało?

No i jeżeli faktycznie jest tak, że tłuką tylko dziedziczeniu, to jak się z niego wyleczyć, żeby nabrać wprawy, kiedy użyć szablonów, żeby nie pourywało rąk.

2

Miałem taki projekt gdzie użyszkodicy składali sobie z gotowych bloczków obróbkę sygnałów cyfrowych.
Na przykład jest dwa bloczki filtr dolny oraz filtr górny składamy z nich filtr pasmowy i zapisujemy jako bloczek przygotowany dla dalszego użycia.
Każdy bloczek ma nieograniczoną ilość wejść i wyjść, cała praca musi być online, użyszkodników dużo trzeba wszystko zsynchronizować i nie pozwolić aby ktoś zamulał cały serwer.
Bez szablonów - nie do przebrnięcia (wg mnie).

0
jarekr000000 napisał(a):
Marcin Marcin napisał(a):

Jakie są przykłady w których można użyć go i dobrze się sprawdzi? Macie jakieś historie gdzie użyliście i faktycznie dobrze działało?

Dla mnie to pytanie w stylu: użyliście kiedyś funkcji i faktycznie dobrze działało ?

Generyki, czy tam templaty to podstawa polimorfizmu (w ogólności, a nie tego jednego rodzaju poliformizmu (Subtyping), który jest tłuczony na OOP) - czyli pisania kodu, tak żeby dało się wielokrotnie użyć - dla różnych typów danych. W języku ze statycznymi typami trudno bez tego żyć.

Faktycznie, to co można zrobić w templatach da się zwykle zrobić alternatywnie przy pomocy subtype, ale zwykle tracimy wtedy dokładne typy i skazujemy się na downcasting - strasznie smutne.

@jarekr000000: czy podałbyś przykład użycia template w c++ gdzie faktycznie ich użycie względem dziedziczenia ma sens?

BartoSAS napisał(a):

Dla mnie to pytanie w stylu: użyliście kiedyś funkcji i faktycznie dobrze działało ?

Szczerze, przyznam się bez bicia, że ciężko mi wymyślić inne miejsce dla szablonów poza kontenerami typu vector. Pewnie za dużo CRUDzenia mam na co dzień, stąd nie przychodzi mi do głowy coś innego, więc z chęcią podbiję to pytanie - gdzie zastosowaliście szablony, że dobrze to zadziałało?

No i jeżeli faktycznie jest tak, że tłuką tylko dziedziczeniu, to jak się z niego wyleczyć, żeby nabrać wprawy, kiedy użyć szablonów, żeby nie pourywało rąk.

@BartoSAS: zgodzę się z Twoją odpowiedzią, brakuje mi dobrego przykładu w którym użycie template miałby sens, może jeszcze za mało kodu napisałem.

Dobry przykład z filtrami podał @_13th_Dragon jednak poza nim nie zauważam dobrego zastosowania dla template. Przeczytałem w książce o C++ o zastosowaniu tej funkcji języka i nie rozumiem kiedy jej używać a kiedy po prostu dziedziczenia.

screenshot-20220918184414.png

0

Proponuję nauczyć się korzystać z gtest-a i gmocka do stopnia takiego, by łączyć złożone matcher'y.
Wtedy na 100% docenisz szablony.

1
Marcin Marcin napisał(a):

@jarekr000000: czy podałbyś przykład użycia template w c++ gdzie faktycznie ich użycie względem dziedziczenia ma sens?

Biorę pierwszy z brzegu przykład z życia wzięty. Liczymy numerycznie całkę Riemanna dla zadanej funkcji - f w podanym zakresie - (a,b), i parametru n oznaczającego ilość przedziałów (np. trapezów) w całkowaniu numerycznym.

0
jarekr000000 napisał(a):
Marcin Marcin napisał(a):

@jarekr000000: czy podałbyś przykład użycia template w c++ gdzie faktycznie ich użycie względem dziedziczenia ma sens?

Biorę pierwszy z brzegu przykład z życia wzięty. Liczymy numerycznie całkę Riemanna dla zadanej funkcji - f w podanym zakresie - (a,b), i parametru n oznaczającego ilość przedziałów (np. trapezów) w całkowaniu numerycznym.

Hm, ciewawe. Niemożliwość użycie dziedziczenia tu wynika z tego że w C++ nie zaprojektowali interfejsu dla liczb (niedorobienie obiektowe?). A żeby użyć szablonu wystarczy że są zaimplementowane te same metody/operatory bez konieczności tworzenia dla nich interfejsu. Czy to podpada pod polimorfizm strukturalny? (statyczną odmianę duck typing?)

4

A może po prostu zaimplementuj sobie ten vektor na dziedziczeniu i templatach, a następnie porównaj sobie rozwiązania.

1

Szablony to jedyny jak na razie sposób by w C++ osiągnąć jakiś racjonalny statyczny polimorfizm, który nadal będzie pod kontrolą kompilatora - statyczny w sensie bez virtual dispatch. Szablony są też jedynym sposobem w C++ by "bawić się" się w metaprogramowanie a nasz kod nadal był pod kontrolą kompilatora. Do tego dalej dochodzą optymalizacje typu rozwijanie pętli, pozbywanie się branchy poprzez pozbywanie się drabinek ifów itd. itp.

Nie jestem jakimś fanbojem szablonów bo ich składnia jest dla mnie problematyczna oraz w gruncie rzeczy techniki na nich oparte to trochę hakowanie prostego mechanizmu kopiowanie jednego kodu w miejsce innego. Tym nie mniej, obiektywnie rzecz biorąc, jak na tak prosty mechanizm daje on szeroki wachlarz możliwości w programowaniu generycznym, metaprogramowaniu i tworzeniu optymalizacji z zachowaniem bezpieczeństwa typów, dlatego nie zamierzam go unikać na siłę.

1

To co zapewniają interfejsy/dziedziczenie (subtyping polimorphism) to jeden z rodzajów polimorfizmu, który pozwala mi abstrachować takie wyrażenie (this).metoda(), gdzie częścią parametryzowaną jest this, czyli instancja typu spełniającej interfejs. I w sumie to tyle, bardzo ograniczony zakres. Polimorfizm może mieć też wiele innych postaci np. chcę mieć funkcję json.Parse<T>(str), przy klasycznym podejściu tego nie możemy zrobić, bo this jest instancją a my parametryzujemy typem. Albo (this) + (this) (przeładowanie operatorów), albo generyki std::vector<T>, gdzie T jest typem i parametryzujemy std::vector od zupełnie innego typu (a nie od typu this jak w poprzednich przykładach). W polimorfizmie chodzi o pisanie kodu w taki sposób, żeby nadawał się do użycia w wielu kontekstach a jego różne odmiany służą temu, żeby dany kod był łatwy do pisania i zrozumienia jednocześnie żeby był aplikowalny w wielu różnych scenariuszach

0
several napisał(a):

void* to nie C++. W C++ nie ma potrzeby używania go do niczego, jeśli nie robimy czegoś z jakąś biblioteką w C. Nazywanie void* odpowiednikiem praobiektu z innych języków w C++ to nadużycie.

A dlaczego? shared_ptr<void> też nie jest C++?
Do jakiegoś prostego type-erasure raczej się nadaje.
Do trzymania user-data również. Jasne, można zamiast tego parametryzować całą klasę, ale w konsekwencji nie da się prosto zrobić z tego kolekcji.
Może nie jest to jakiś feature pierwszej potrzeby, ale na pewno nie wykluczałbym go kompletnie z C++.

1 użytkowników online, w tym zalogowanych: 0, gości: 1