Mam na myśli, poza obowiązującą nazwą (Standardowa biblioteka szablonów) ...
C ++ początkowo zamierzał prezentować koncepcje OOP w C. To znaczy: możesz powiedzieć, co konkretna jednostka może, a czego nie może zrobić (niezależnie od tego, jak to robi) na podstawie swojej klasy i hierarchii klas. Niektóre kompozycje umiejętności są trudniejsze do opisania w ten sposób ze względu na problematykę wielokrotnego dziedziczenia oraz fakt, że C ++ obsługuje koncepcję interfejsów w nieco niezdarny sposób (w porównaniu z Javą itp.), Ale jest tam (i może być ulepszony).
Potem do gry weszły szablony wraz z STL. Wydawało się, że STL bierze klasyczne koncepcje OOP i spuszcza je do ścieków, używając zamiast tego szablonów.
Należy rozróżnić przypadki, w których szablony są używane do uogólnienia typów, w których same motywy typów są nieistotne dla działania szablonu (pojemniki, na przykład). Posiadanie vector<int>
ma sens.
Jednak w wielu innych przypadkach (iteratory i algorytmy) typy szablonów powinny podążać za „koncepcją” (Iterator wejściowy, Iterator do przodu itp.), W której rzeczywiste szczegóły koncepcji są definiowane całkowicie przez wdrożenie szablonu funkcja / klasa, a nie klasa typu użytego w szablonie, co jest nieco przeciwdziałaniem OOP.
Na przykład możesz powiedzieć funkcji:
void MyFunc(ForwardIterator<...> *I);
Aktualizacja: Jak było niejasne w pierwotnym pytaniu, ForwardIterator jest w porządku, aby sam szablonować, aby umożliwić dowolny typ ForwardIterator. Przeciwnie, jest koncepcja ForwardIterator.
oczekuje iteratora do przodu tylko na podstawie jego definicji, w której należy spojrzeć na implementację lub dokumentację:
template <typename Type> void MyFunc(Type *I);
Dwa twierdzenia, które mogę wysunąć na korzyść korzystania z szablonów: skompilowany kod można zwiększyć, dostosowując szablon do każdego używanego typu, zamiast używać vtables. Oraz fakt, że szablonów można używać z rodzimymi typami.
Jednak szukam głębszego powodu, dla którego porzucenie klasycznego OOP na rzecz szablonowania dla STL? (Zakładając, że przeczytałeś tak daleko: P)
vector<int>
ivector<char>
być używane w tym samym czasie. Mogą, oczywiście, ale możesz użyć dwóch dowolnych fragmentów kodu w tym samym czasie. To nie ma nic wspólnego z szablonami, C ++ lub STL. Nie ma niczego, w którym wystąpieniuvector<int>
wymagavector<char>
się załadowania lub wykonania kodu.Odpowiedzi:
Krótka odpowiedź brzmi „ponieważ C ++ się zmieniło”. Tak, pod koniec lat 70. Stroustrup zamierzał stworzyć ulepszone C z funkcjami OOP, ale to już dawno. Do czasu ustandaryzowania języka w 1998 r. Nie był to już język OOP. To był język paradygmatu. Z pewnością miał pewne wsparcie dla kodu OOP, ale miał także nałożony kompletny język szablonu, pozwalał na metaprogramowanie w czasie kompilacji, a ludzie odkryli ogólne programowanie. Nagle OOP nie wydawało się aż tak ważne. Nie wtedy, gdy możemy napisać prostszy, bardziej zwięzły i wydajniejszy kod za pomocą technik dostępnych za pośrednictwem szablonów i ogólnego programowania.
OOP nie jest świętym Graalem. To uroczy pomysł, który w latach 70. był lepszy od języków proceduralnych, kiedy został wymyślony. Ale szczerze mówiąc, to nie wszystko. W wielu przypadkach jest niezdarny i pełny i tak naprawdę nie promuje kodu wielokrotnego użytku ani modułowości.
Właśnie dlatego społeczność C ++ jest dziś znacznie bardziej zainteresowana programowaniem ogólnym i dlatego wszyscy zaczynają zdawać sobie sprawę, że programowanie funkcjonalne jest również dość sprytne. OOP samo w sobie nie jest ładnym widokiem.
Spróbuj narysować wykres zależności hipotetycznej STL „OOP-ified”. Ile klas musiałoby się o sobie dowiedzieć? Byłoby wiele zależności. Czy byłbyś w stanie dołączyć tylko
vector
nagłówek, bez wciągania,iterator
a nawetiostream
wciągania? STL ułatwia to. Wektor wie o zdefiniowanym typie iteratora i to wszystko. Algorytmy STL nic nie wiedzą . Nie muszą nawet zawierać nagłówka iteratora, mimo że wszyscy akceptują iteratory jako parametry. Który jest więc bardziej modułowy?STL może nie przestrzegać reguł OOP, jak definiuje to Java, ale czy nie osiąga celów OOP? Czy nie zapewnia możliwości ponownego użycia, niskiego sprzężenia, modułowości i enkapsulacji?
I czy nie osiąga tych celów lepiej niż w przypadku wersji OOP?
Jeśli chodzi o to, dlaczego STL został przyjęty w tym języku, wydarzyło się kilka rzeczy, które doprowadziły do STL.
Najpierw szablony zostały dodane do C ++. Zostały dodane z tego samego powodu, dla którego generyczne zostały dodane do .NET. Dobrym pomysłem było pisanie takich rzeczy jak „kontenery typu T” bez wyrzucania bezpieczeństwa typu. Oczywiście wdrożenie, na którym się zdecydowali, było znacznie bardziej złożone i potężne.
Następnie ludzie odkryli, że dodany przez nich mechanizm szablonów był jeszcze potężniejszy niż oczekiwano. I ktoś zaczął eksperymentować z użyciem szablonów do napisania bardziej ogólnej biblioteki. Zainspirowany programowaniem funkcjonalnym i wykorzystujący wszystkie nowe możliwości C ++.
Przedstawił go komitetowi językowemu C ++, który przyzwyczaił się do niego, ponieważ wyglądał tak dziwnie i inaczej, ale ostatecznie zdał sobie sprawę, że działa lepiej niż tradycyjne odpowiedniki OOP, które musieliby uwzględnić w innym przypadku . Wprowadzili więc kilka zmian i zaadaptowali do standardowej biblioteki.
Nie był to wybór ideologiczny, nie był to wybór polityczny „chcemy być OOP czy nie”, ale bardzo pragmatyczny. Ocenili bibliotekę i zobaczyli, że działa ona bardzo dobrze.
W każdym razie oba powody, dla których wspominasz o STL, są absolutnie niezbędne.
Standardowa biblioteka C ++ musi być wydajna. Jeśli jest mniej wydajny niż, powiedzmy, równoważny ręcznie zwijany kod C, ludzie nie będą go używać. Obniżyłoby to produktywność, zwiększyło prawdopodobieństwo błędów i ogólnie było po prostu złym pomysłem.
A STL musi współpracować z typami pierwotnymi, ponieważ typy pierwotne są wszystkim, co masz w C, i są główną częścią obu języków. Gdyby STL nie działał z natywnymi tablicami, byłby bezużyteczny .
Twoje pytanie ma silne założenie, że OOP jest „najlepszy”. Jestem ciekawy, dlaczego. Pytasz, dlaczego „porzucili klasyczne OOP”. Zastanawiam się, dlaczego mieliby się tego trzymać. Jakie miałoby to zalety?
źródło
std::set
jako przykład. Nie dziedziczy po abstrakcyjnej klasie bazowej. Jak to ogranicza twoje użyciestd::set
? Czy jest coś, czego nie można zrobić,std::set
ponieważ nie dziedziczy po abstrakcyjnej klasie bazowej?Najbardziej bezpośrednią odpowiedzią na to, o co myślę, że pytasz / narzekasz, jest następująca: założenie, że C ++ jest językiem OOP, jest błędnym założeniem.
C ++ jest językiem opartym na wielu paradygmatach. Można go programować przy użyciu zasad OOP, można programować programowo, można programować ogólnie (szablony), a w C ++ 11 (wcześniej znany jako C ++ 0x) niektóre rzeczy można nawet programować funkcjonalnie.
Projektanci C ++ postrzegają to jako zaletę, dlatego twierdzą, że ograniczenie C ++ do działania jak język wyłącznie OOP, gdy programowanie ogólne rozwiązuje problem lepiej i, cóż bardziej ogólnie , byłoby krokiem wstecz.
źródło
Rozumiem, że Stroustrup początkowo preferował projekt kontenera w stylu „OOP” i w rzeczywistości nie widział innego sposobu, aby to zrobić. Alexander Stepanov jest odpowiedzialny za STL, a jego cele nie obejmowały „zorientowania obiektowego” :
(Wyjaśnia, dlaczego dziedziczenie i wirtuozeria - czyli projekt obiektowy „był zasadniczo wadliwy i nie należy go używać” w dalszej części wywiadu).
Kiedy Stepanov zaprezentował swoją bibliotekę Stroustrupowi, Stroustrup i inni przeszli herkulesowe wysiłki, aby wprowadzić go do standardu ISO C ++ (ten sam wywiad):
źródło
Odpowiedź znajduje się w tym wywiadzie ze Stepanovem, autorem STL:
źródło
Dlaczego czysty projekt OOP do biblioteki struktury danych i algorytmów byłby lepszy? OOP nie jest rozwiązaniem dla wszystkich rzeczy.
IMHO, STL to najbardziej elegancka biblioteka, jaką kiedykolwiek widziałem :)
na twoje pytanie
nie potrzebujesz polimorfizmu środowiska uruchomieniowego, STL ma tę zaletę, że implementuje bibliotekę przy użyciu polimorfizmu statycznego, co oznacza wydajność. Spróbuj napisać ogólny algorytm sortowania lub odległości lub jakikolwiek inny algorytm dotyczący WSZYSTKICH kontenerów! Twoje Sortowanie w Javie wywoływałoby funkcje, które są dynamiczne na n-poziomach do wykonania!
Potrzebujesz głupoty, takiej jak boks i rozpakowanie, aby ukryć paskudne założenia tak zwanych języków Pure OOP.
Jedyny problem, jaki widzę w STL i ogólnie szablonach, to okropne komunikaty o błędach. Które zostaną rozwiązane przy użyciu Koncepcji w C ++ 0X.
Porównywanie STL do kolekcji w Javie jest jak porównywanie Taj Mahal do mojego domu :)
źródło
static_assert
być może.Myślę, że źle rozumiesz zamierzone użycie pojęć według szablonów. Na przykład Iterator do przodu jest bardzo dobrze zdefiniowaną koncepcją. Aby znaleźć wyrażenia, które muszą być poprawne, aby klasa mogła być iteratorem do przodu, oraz ich semantykę, w tym złożoność obliczeniową, zapoznaj się ze standardem lub na stronie http://www.sgi.com/tech/stl/ForwardIterator.html (aby zobaczyć to wszystko, musisz użyć linków do Input, Output i Trivial Iterator).
Ten dokument jest doskonale dobrym interfejsem, a „rzeczywiste szczegóły koncepcji” są zdefiniowane właśnie tam. Nie są one zdefiniowane przez implementacje Iteratorów do przodu, ani nie są zdefiniowane przez algorytmy wykorzystujące Iteratory do przodu.
Różnice w obsłudze interfejsów między STL i Javą są trzykrotne:
1) STL definiuje prawidłowe wyrażenia za pomocą obiektu, podczas gdy Java definiuje metody, które muszą być wywoływalne na obiekcie. Oczywiście prawidłowe wyrażenie może być wywołaniem metody (funkcji członka), ale nie musi tak być.
2) Interfejsy Java są obiektami wykonawczymi, podczas gdy koncepcje STL nie są widoczne w środowisku wykonawczym, nawet przy RTTI.
3) Jeśli nie sprawdzisz poprawnych wymaganych poprawnych wyrażeń dla koncepcji STL, pojawi się nieokreślony błąd kompilacji, gdy utworzysz szablon z tym typem. Jeśli nie uda się wdrożyć wymaganej metody interfejsu Java, pojawi się komunikat o błędzie kompilacji.
Trzecia część dotyczy sytuacji, gdy podoba Ci się rodzaj „pisania kaczego”: interfejsy mogą być niejawne. W Javie interfejsy są dość wyraźne: klasa „jest” Iterowalna tylko wtedy, gdy mówi, że implementuje Iterable. Kompilator może sprawdzić, czy wszystkie podpisy jego metod są obecne i poprawne, ale semantyka jest nadal niejawna (tj. Jest albo udokumentowana, czy nie, ale tylko więcej kodu (testy jednostkowe) może powiedzieć, czy implementacja jest poprawna).
W C ++, podobnie jak w Pythonie, zarówno semantyka, jak i składnia są niejawne, chociaż w C ++ (i w Pythonie, jeśli masz preprocesor silnego pisania), otrzymujesz pomoc od kompilatora. Jeśli programista wymaga jawnej deklaracji interfejsów podobnej do języka Java przez klasę implementującą, wówczas standardowym podejściem jest użycie cech typu (a wielokrotne dziedziczenie może zapobiec zbyt szczegółowemu określeniu). W porównaniu z Javą brakuje jednego szablonu, który mogę utworzyć z moim typem i który skompiluje się wtedy i tylko wtedy, gdy wszystkie wymagane wyrażenia będą poprawne dla mojego typu. To powiedziałoby mi, czy zaimplementowałem wszystkie wymagane bity „zanim go użyję”. To wygoda, ale nie jest to podstawa OOP (i nadal nie testuje semantyki,
STL może, ale nie musi, być wystarczająco OO dla twojego gustu, ale z pewnością oddziela interfejs czysto od implementacji. Brakuje mu zdolności Java do refleksji nad interfejsami i inaczej raportuje naruszenia wymagań interfejsu.
Osobiście uważam, że typy ukryte są siłą, jeśli są właściwie stosowane. Algorytm mówi, co robi ze swoimi parametrami szablonu, a implementator upewnia się, że te rzeczy działają: jest to dokładnie wspólny mianownik tego, co powinny robić „interfejsy”. Ponadto w przypadku STL jest mało prawdopodobne, abyś używał, powiedzmy,
std::copy
na podstawie znalezienia swojej deklaracji przekazywania w pliku nagłówkowym. Programiści powinni opracowywać to, co funkcja bierze na podstawie dokumentacji, a nie tylko podpisu funkcji. Dzieje się tak w C ++, Python lub Java. Istnieją ograniczenia dotyczące tego, co można osiągnąć za pomocą pisania w dowolnym języku, a próba pisania na klawiaturze w celu zrobienia czegoś, czego nie robi (sprawdź semantykę) byłaby błędem.To powiedziawszy, algorytmy STL zwykle nazywają parametry swoich szablonów w sposób, który wyjaśnia, która koncepcja jest wymagana. Ma to jednak na celu dostarczenie użytecznych dodatkowych informacji w pierwszym wierszu dokumentacji, a nie uczynienie deklaracji przekazywanych bardziej pouczającymi. Jest więcej rzeczy, które musisz wiedzieć, niż można zawrzeć w typach parametrów, więc musisz przeczytać dokumenty. (Na przykład w algorytmach, które przyjmują zakres wejściowy i iterator wyjściowy, są szanse, że iterator wyjściowy potrzebuje wystarczającej „przestrzeni” na pewną liczbę wyników na podstawie wielkości zakresu wejściowego i być może zawartych w nim wartości. Spróbuj mocno to wpisać. )
Oto Bjarne na temat wyraźnie zadeklarowanych interfejsów: http://www.artima.com/cppsource/cpp0xP.html
Patrząc na to odwrotnie, za pomocą pisania kaczego można zaimplementować interfejs, nie wiedząc, że interfejs istnieje. Albo ktoś może napisać interfejs celowo, tak aby klasa go zaimplementowała, po skonsultowaniu się z dokumentami, aby zobaczyć, że nie prosi o nic, czego jeszcze nie zrobiłeś. To jest elastyczne.
źródło
std
biblioteki czegoś, co nie pasuje do koncepcji, jest zwykle „źle sformułowane, nie wymaga diagnostyki”.„OOP oznacza dla mnie tylko przesyłanie wiadomości, lokalne przechowywanie i ochronę oraz ukrywanie procesów państwowych oraz ekstremalne późne wiązanie wszystkich rzeczy. Można to zrobić w Smalltalk i LISP. Możliwe są inne systemy, w których jest to możliwe, ale Nie jestem ich świadomy ”. - Alan Kay, twórca Smalltalk.
C ++, Java i większość innych języków są dalekie od klasycznego OOP. To powiedziawszy, argumentowanie za ideologiami nie jest zbyt produktywne. C ++ nie jest w żadnym sensie czysty, więc implementuje funkcjonalność, która wydaje się mieć w tym czasie pragmatyczny sens.
źródło
Firma STL rozpoczęła działalność z zamiarem zapewnienia dużej biblioteki obejmującej najczęściej używany algorytm - w celu zapewnienia spójnego zachowania i wydajności . Szablon stał się kluczowym czynnikiem umożliwiającym wdrożenie i osiągnięcie celu.
Aby podać kolejne odniesienie:
Al Stevens przeprowadza wywiad z DDJ Alexem Stepanovem:
Stepanov wyjaśnił swoje doświadczenie zawodowe i wybór dotyczący dużej biblioteki algorytmów, która ostatecznie przekształciła się w STL.
źródło
Podstawowy problem z
jak w bezpieczny sposób uzyskać rodzaj rzeczy, którą zwraca iterator? W przypadku szablonów jest to wykonywane w momencie kompilacji.
źródło
Przez chwilę pomyślmy o standardowej bibliotece jako w zasadzie bazie danych kolekcji i algorytmów.
Jeśli studiowałeś historię baz danych, niewątpliwie wiesz, że na początku bazy danych były w większości „hierarchiczne”. Hierarchiczne bazy danych bardzo ściśle odpowiadały klasycznemu OOP - a konkretnie odmianie z jednym dziedzictwem, takim jak stosowane przez Smalltalk.
Z czasem stało się jasne, że hierarchiczne bazy danych można wykorzystać do modelowania prawie wszystkiego, ale w niektórych przypadkach model z pojedynczym spadkiem był dość ograniczający. Jeśli miałeś drewniane drzwi, warto było spojrzeć na nie albo jako drzwi, albo na kawałek surowca (stal, drewno itp.)
Tak więc wymyślili bazy danych modeli sieciowych. Bazy danych modeli sieciowych bardzo ściśle odpowiadają wielokrotnemu dziedziczeniu. C ++ całkowicie obsługuje wielokrotne dziedziczenie, podczas gdy Java obsługuje ograniczoną formę (możesz dziedziczyć tylko z jednej klasy, ale możesz także implementować tyle interfejsów, ile chcesz).
Zarówno bazy danych modelu hierarchicznego, jak i modelu sieci przeważnie przestały być używane ogólnego przeznaczenia (choć kilka pozostaje w dość specyficznych niszach). W większości przypadków zostały one zastąpione relacyjnymi bazami danych.
Wiele powodów, dla których relacyjne bazy danych przejęły, to wszechstronność. Model relacyjny jest funkcjonalnie nadzbiorem modelu sieciowego (który z kolei jest nadzbiorem modelu hierarchicznego).
C ++ w dużej mierze podążył tą samą ścieżką. Zgodność między pojedynczym spadkiem a modelem hierarchicznym oraz między wielokrotnym spadkiem a modelem sieci jest dość oczywista. Zgodność między szablonami C ++ a modelem hierarchicznym może być mniej oczywista, ale i tak jest dość ścisła.
Nie widziałem tego formalnie, ale wierzę, że możliwości szablonów są nadzbiorem tych zapewnianych przez wielokrotne dziedziczenie (co jest wyraźnie nadzbiorem pojedynczego dziedziczenia). Jedyną trudną częścią jest to, że szablony są w większości związane statycznie - to znaczy, że całe wiązanie odbywa się w czasie kompilacji, a nie w czasie wykonywania. Jako taki, formalny dowód, że dziedziczenie stanowi nadzór nad możliwościami dziedziczenia, może być nieco trudny i złożony (lub nawet niemożliwy).
W każdym razie myślę, że to jest prawdziwy powód, dla którego C ++ nie używa dziedziczenia dla swoich kontenerów - nie ma prawdziwego powodu, aby to zrobić, ponieważ dziedziczenie zapewnia tylko podzbiór możliwości zapewnianych przez szablony. Ponieważ szablony są w niektórych przypadkach koniecznością, równie dobrze można ich używać niemal wszędzie.
źródło
Jak robisz porównania z ForwardIterator *? To znaczy, w jaki sposób sprawdzasz, czy przedmiot, którego szukasz, jest tym, czego szukasz, czy przeszedłeś?
Przez większość czasu używałbym czegoś takiego:
co oznacza, że wiem, że wskazuję na MyType i wiem, jak je porównać. Chociaż wygląda jak szablon, tak naprawdę nie jest (brak słowa kluczowego „szablon”).
źródło
To pytanie ma wiele wspaniałych odpowiedzi. Należy również wspomnieć, że szablony obsługują otwarty projekt. Przy obecnym stanie zorientowanych obiektowo języków programowania, do radzenia sobie z takimi problemami należy używać wzorca gościa, a prawdziwy OOP powinien obsługiwać wielokrotne dynamiczne wiązanie. Zobacz Open Multi-Methods dla C ++, P. Pirkelbauer i in.za bardzo ciekawe czytanie.
Innym interesującym punktem szablonów jest to, że można ich używać również do polimorfizmu środowiska wykonawczego. Na przykład
Zauważ, że ta funkcja będzie działać również, jeśli
Value
jest w pewnym sensie wektorem ( nie std :: vector, który należy wywołać)std::dynamic_array
aby uniknąć pomyłek)Jeśli
func
jest mała, ta funkcja wiele zyska na wstawianiu. Przykładowe użycieW takim przypadku powinieneś znać dokładną odpowiedź (2.718 ...), ale łatwo jest zbudować proste ODE bez elementarnego rozwiązania (wskazówka: użyj wielomianu w y).
Teraz masz dużą ekspresję
func
i używasz solvera ODE w wielu miejscach, więc twój plik wykonywalny jest zanieczyszczony instancjami szablonu wszędzie. Co robić? Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że działa wskaźnik zwykłej funkcji. Następnie chcesz dodać curry, aby napisać interfejs i jawną instancjęAle powyższa instancja działa tylko dla
double
, dlaczego nie napisać interfejsu jako szablonu:i specjalizujemy się w niektórych popularnych typach wartości:
Gdyby funkcja została najpierw zaprojektowana wokół interfejsu, musiałbyś dziedziczyć po ABC. Teraz masz tę opcję, a także wskaźnik funkcji, lambda lub dowolny inny obiekt funkcji. Kluczem tutaj jest to, że musimy mieć
operator()()
i musimy być w stanie korzystać z niektórych operatorów arytmetycznych na jego typie zwrotu. Zatem maszyna szablonów zepsułaby się w tym przypadku, gdyby C ++ nie miał przeciążenia operatora.źródło
Koncepcja oddzielenia interfejsu od interfejsu i możliwości wymiany implementacji nie jest nieodłączna od programowania obiektowego. Uważam, że to pomysł, który został stworzony podczas rozwoju opartego na komponentach, takiego jak Microsoft COM. (Zobacz moją odpowiedź na temat rozwoju opartego na komponentach?) Dorastając i ucząc się języka C ++, ludzie dostali dziedzictwo i polimorfizm. Dopiero w latach 90. ludzie zaczęli mówić „program do„ interfejsu ”, a nie„ implementacja ”i„ faworyzowanie ”składu obiektów nad„ dziedziczenie klas ”. (przy okazji oba cytowane z GoF).
Potem pojawiła się Java z wbudowanym śmieciarzem i
interface
słowem kluczowym i nagle stało się praktyczne oddzielenie interfejsu i implementacji. Zanim się zorientujesz, pomysł stał się częścią OO. C ++, szablony i STL poprzedzają to wszystko.źródło