Czytałem powiązane pytanie Czy istnieją jakieś wzorce projektowe, które są niepotrzebne w dynamicznych językach, takich jak Python? i pamiętam ten cytat na Wikiquote.org
Wspaniałą rzeczą w dynamicznym pisaniu jest to, że pozwala wyrazić wszystko, co jest obliczalne. A systemy typu nie-systemy są zazwyczaj rozstrzygalne i ograniczają cię do podzbioru. Ludzie, którzy preferują układy statyczne, mówią: „jest w porządku, jest wystarczająco dobry; wszystkie ciekawe programy, które chcesz napisać, będą działały jak typy ”. Ale to niedorzeczne - kiedy już masz system typów, nie wiesz nawet, jakie są interesujące programy.
--- Software Engineering Radio Episode 140: Newspeak and Pluggable Types with Gilad Bracha
Zastanawiam się, czy istnieją użyteczne wzorce projektowe lub strategie, które wykorzystując sformułowanie cytatu „nie działają jak typy”?
źródło
Odpowiedzi:
Typy pierwszej klasy
Pisanie dynamiczne oznacza, że masz typy pierwszej klasy: możesz sprawdzać, tworzyć i przechowywać typy w czasie wykonywania, w tym typy własne języka. Oznacza to również, że wartości są wpisywane, a nie zmienne .
Język o typie statycznym może generować kod, który również opiera się na typach dynamicznych, takich jak wysyłanie metod, klasy typów itp., Ale w sposób ogólnie niewidoczny dla środowiska wykonawczego. W najlepszym razie dają ci możliwość przeprowadzenia introspekcji. Alternatywnie możesz symulować typy jako wartości, ale wtedy masz dynamiczny system typów ad-hoc.
Jednak systemy typów dynamicznych rzadko mają tylko typy pierwszej klasy. Możesz mieć pierwszorzędne symbole, pierwszorzędne pakiety, pierwszorzędne ... wszystko. Jest to w przeciwieństwie do ścisłego oddzielenia języka kompilatora od języka wykonawczego w językach o typie statycznym. To, co potrafi kompilator lub interpreter, może również wykonać środowisko wykonawcze.
Teraz zgódźmy się, że wnioskowanie o typie jest dobrą rzeczą i że chcę sprawdzić mój kod przed jego uruchomieniem. Lubię też tworzyć i kompilować kod w czasie wykonywania. Uwielbiam też wstępnie obliczać rzeczy w czasie kompilacji. W języku pisanym dynamicznie odbywa się to w tym samym języku. W OCaml masz system typu moduł / funktor, który różni się od głównego systemu typów, który różni się od języka preprocesora. W C ++ masz język szablonów, który nie ma nic wspólnego z głównym językiem, który na ogół nie zna typów podczas wykonywania. I to jest w porządku w tym języku, ponieważ nie chcą więcej.
Ostatecznie nie zmienia to tak naprawdę rodzaju oprogramowania, które można opracować, ale ekspresja zmienia sposób , w jaki je tworzysz i czy jest to trudne, czy nie.
Wzory
Wzorce oparte na typach dynamicznych to wzorce obejmujące środowiska dynamiczne: klasy otwarte, dyspozytorskie, bazy danych obiektów w pamięci, serializacja itp. Proste rzeczy, takie jak ogólne pojemniki działają, ponieważ wektor nie zapomina w czasie wykonywania o typie obiektów, które posiada (nie ma potrzeby stosowania typów parametrycznych).
Próbowałem przedstawić wiele sposobów oceny kodu we Common Lisp, a także przykłady możliwych analiz statycznych (jest to SBCL). Przykład piaskownicy kompiluje niewielki podzbiór kodu Lisp pobrany z osobnego pliku. Aby być względnie bezpiecznym, zmieniam czytelność, zezwalam tylko na podzbiór standardowych symboli i zawijam czas.
Nic powyżej nie jest „niemożliwe” w przypadku innych języków. Podejście plug-in w Blenderze, w oprogramowaniu muzycznym lub IDE dla języków skompilowanych statycznie, które wykonują rekompilację „w locie” itp. Zamiast narzędzi zewnętrznych dynamiczne języki preferują narzędzia, które wykorzystują informacje, które już tam są. Wszyscy znani rozmówcy FOO? wszystkie podklasy BAR? wszystkie metody, które są wyspecjalizowane przez klasę ZOT? to są zinternalizowane dane. Typy to tylko kolejny aspekt tego.
(patrz także: CFFI )
źródło
Krótka odpowiedź: nie, ponieważ równoważność Turinga.
Długa odpowiedź: ten facet jest trollem. Chociaż prawdą jest, że systemy typów „ograniczają cię do podzbioru”, rzeczy poza tym podzbiorem są z definicji rzeczami, które nie działają.
Wszystko, co możesz zrobić w dowolnym języku programowania kompletnym Turinga (który jest językiem zaprojektowanym do programowania ogólnego, plus wiele innych, których nie ma; to dość niski pasek do wyczyszczenia i istnieje kilka przykładów systemu, który staje się Turing- niezamierzone wypełnienie) możesz zrobić w dowolnym innym języku programowania Turing-complete. Nazywa się to „równoważnością Turinga” i oznacza tylko dokładnie to, co mówi. Co ważne, nie oznacza to, że możesz zrobić drugą rzecz równie łatwo w innym języku - niektórzy twierdzą, że o to właśnie chodzi w tworzeniu nowego języka programowania: aby dać ci lepszy sposób na wykonanie pewnych czynności rzeczy, które ssą istniejące języki.
Na przykład system typu dynamicznego można emulować na podstawie statycznego systemu typu OO, po prostu deklarując wszystkie zmienne, parametry i zwracane wartości jako
Object
typ podstawowy, a następnie używając refleksji, aby uzyskać dostęp do określonych danych wewnątrz, więc kiedy sobie to uświadomisz widać, że dosłownie nic nie można zrobić w języku dynamicznym, czego nie można zrobić w języku statycznym. Ale zrobienie tego w ten sposób byłoby oczywiście ogromnym bałaganem.Facet z cytatu ma rację, że typy statyczne ograniczają to, co możesz zrobić, ale to ważna funkcja, a nie problem. Linie na drodze ograniczają to, co możesz zrobić w samochodzie, ale czy uważasz je za restrykcyjne lub pomocne? (Wiem, że nie chciałbym jeździć po ruchliwej, złożonej drodze, na której nic nie mówi samochodom jadącym w przeciwnym kierunku, aby trzymały się z boku i nie podjeżdżały tam, gdzie jadę!) Ustalając zasady, które jasno określają, co jest uważane za nieprawidłowe zachowanie i zapewniające, że tak się nie stanie, znacznie zmniejszasz ryzyko wystąpienia nieprzyjemnego wypadku.
Poza tym źle opisuje drugą stronę. Nie chodzi o to, że „wszystkie ciekawe programy, które chcesz pisać, będą działały jak typy”, ale „wszystkie interesujące programy, które chcesz pisać, będą wymagać typów”. Po przekroczeniu pewnego poziomu złożoności bardzo trudno jest utrzymać bazę kodu bez systemu typów, który utrzymywałby Cię w linii z dwóch powodów.
Po pierwsze, ponieważ kod bez adnotacji typu jest trudny do odczytania. Rozważ następujący Python:
Jak wyglądają dane, które odbiera system na drugim końcu połączenia? A jeśli odbiera coś, co wygląda zupełnie nie tak, jak możesz dowiedzieć się, co się dzieje?
Wszystko zależy od struktury
value.someProperty
. Ale jak to wygląda? Dobre pytanie! Co znajduje się powołaniesendData()
? Co to przechodzi? Jak wygląda ta zmienna? Skąd to się wzieło? Jeśli nie jest lokalny, musisz prześledzić całą historię,value
aby śledzić, co się dzieje. Może przekazujesz coś innego, co również masomeProperty
właściwość, ale nie robi tego, co myślisz, że ma?Teraz spójrzmy na to z adnotacjami typu, jak możesz zobaczyć w języku Boo, który używa bardzo podobnej składni, ale jest statycznie wpisany:
Jeśli coś pójdzie nie tak, nagle zadanie debugowania stało się o rząd wielkości łatwiejsze: spójrz na definicję
MyDataType
! Dodatkowo szansa na złe zachowanie, ponieważ minąłeś jakiś niezgodny typ, który również ma właściwość o tej samej nazwie, nagle spada do zera, ponieważ system typów nie pozwoli ci popełnić tego błędu.Drugi powód opiera się na pierwszym: w dużym i złożonym projekcie najprawdopodobniej masz wielu współpracowników. (A jeśli nie, to budujesz go sam przez długi czas, co jest w zasadzie to samo. Spróbuj przeczytać kod napisany 3 lata temu, jeśli mi nie wierzysz!) Oznacza to, że nie wiesz, co było przechodząc przez głowę osoby, która napisała prawie każdą część kodu w momencie, gdy go napisali, ponieważ nie było cię tam lub nie pamiętasz, czy to był twój własny kod dawno temu. Posiadanie deklaracji typu naprawdę pomaga zrozumieć, jaki był zamiar kodu!
Ludzie tacy jak facet w cytacie często mylnie opisują zalety pisania statycznego jako „pomoc kompilatorowi” lub „wszystko o wydajności” w świecie, w którym prawie nieograniczone zasoby sprzętowe sprawiają, że z każdym rokiem staje się to coraz mniej istotne. Ale jak wykazałem, chociaż te korzyści z pewnością istnieją, podstawową korzyścią są czynniki ludzkie, w szczególności czytelność kodu i łatwość konserwacji. (Jednak dodatkowa wydajność to z pewnością niezły bonus!)
źródło
Zamierzam przejść boczną część „wzorca”, ponieważ myślę, że przekształca się ona w definicję tego, co jest wzorzec lub nie, i od dawna straciłem zainteresowanie tą debatą. Powiem tylko, że są rzeczy, które możesz zrobić w niektórych językach, a których nie możesz zrobić w innych. Pozwólcie, że wyrażę się jasno, nie mówię, że istnieją problemy, które można rozwiązać w jednym języku, których nie można rozwiązać w innym języku. Mason wskazał już na kompletność Turinga.
Na przykład napisałem klasę w Pythonie, która pobiera element XML DOM i zamienia go w obiekt pierwszej klasy. Oznacza to, że możesz napisać kod:
i masz zawartość tej ścieżki w parsowanym obiekcie XML. trochę schludnie i schludnie, IMO. A jeśli nie ma węzła głównego, to po prostu zwraca fikcyjne obiekty, które zawierają jedynie fikcyjne obiekty (żółwie do samego końca). Nie ma prawdziwego sposobu na zrobienie tego w, powiedzmy, Javie. Trzeba było wcześniej skompilować klasę opartą na pewnej wiedzy o strukturze XML. Odkładając na bok, czy to dobry pomysł, tego rodzaju rzeczy naprawdę zmieniają sposób rozwiązywania problemów w dynamicznym języku. Nie twierdzę jednak, że zmienia się w sposób, który zawsze musi być zawsze lepszy. Podejście dynamiczne wiąże się z pewnymi kosztami, a odpowiedź Masona daje dobry przegląd. To, czy są dobrym wyborem, zależy od wielu czynników.
Na marginesie, możesz to zrobić w Javie, ponieważ możesz zbudować interpreter Pythona w Javie . Fakt, że rozwiązanie konkretnego problemu w danym języku może oznaczać budowę tłumacza lub coś podobnego, jest często pomijany, gdy ludzie mówią o kompletności Turinga.
źródło
IDynamicMetaObjectProvider
, aw Boo jest to bardzo proste. ( Oto implementacja w mniej niż 100 wierszach, zawarta jako część standardowego drzewa źródeł w GitHub, ponieważ to takie proste!)"IDynamicMetaObjectProvider"
? Czy to jest związane zedynamic
słowem kluczowym C # ? ... który skutecznie po prostu dynamicznie pisze na C #? Nie jestem pewien, czy twój argument jest ważny, jeśli mam rację.dynamic
osiąga się w języku C #. „Refleksje i wyszukiwania słownikowe” mają miejsce w czasie wykonywania, a nie w czasie kompilacji. Naprawdę nie jestem pewien, jak można stwierdzić, że nie dodaje dynamicznego pisania do języka. Chodzi mi o to, że ostatni akapit Jimmy'ego to obejmuje.Cytat jest poprawny, ale także bardzo nieszczery. Podzielmy to, aby zobaczyć, dlaczego:
Cóż, niezupełnie. Język z dynamicznego typowania pozwala wyrazić coś tak długo, jak to Turinga kompletne , których większość jest. Sam system typów nie pozwala wyrazić wszystkiego. Dajmy mu jednak wątpliwości.
To prawda, ale zauważmy, że teraz zdecydowanie mówimy o tym, na co pozwala system typów , a nie o to, na jaki język, który używa systemu typów. Chociaż możliwe jest użycie systemu typów do obliczania rzeczy w czasie kompilacji, nie jest to generalnie Turing zakończony (ponieważ system typów jest generalnie rozstrzygalny), ale prawie każdy statycznie typowany język jest również Turing ukończony w czasie wykonywania (języki zależne nie, ale nie sądzę, że tutaj o nich rozmawiamy).
Problem polega na tym, że dynamicznie typy języków mają typ statyczny. Czasami wszystko jest ciągiem znaków, a częściej istnieje pewien znakowany związek, w którym każda rzecz jest albo zbiorem właściwości, albo wartością taką jak int lub double. Problem polega na tym, że języki statyczne również mogą to robić, historycznie było to trochę bardziej skomplikowane, ale współczesne języki o typie statycznym sprawiają, że jest to tak samo łatwe, jak w przypadku używania języka typów dynamicznych, więc jak może być różnica w co programista może uznać za interesujący program? Języki statyczne mają dokładnie takie same oznaczone związki, jak i inne typy.
Aby odpowiedzieć na pytanie w tytule: Nie, nie ma wzorców projektowych, których nie można zaimplementować w języku o typie statycznym, ponieważ zawsze można zaimplementować wystarczającą liczbę dynamicznych systemów, aby je uzyskać. Mogą istnieć wzorce, które otrzymujesz za darmo w dynamicznym języku; dla YMMV może to być lub nie być warte znoszenia wad tych języków .
źródło
Z pewnością są rzeczy, które można wykonywać tylko w dynamicznie pisanych językach. Ale niekoniecznie byłyby dobrym projektem.
Możesz przypisać najpierw liczbę całkowitą 5, a następnie ciąg znaków
'five'
lubCat
obiekt do tej samej zmiennej. Ale utrudniasz tylko czytelnikowi twojego kodu zrozumienie, co się dzieje, jaki jest cel każdej zmiennej.Możesz dodać nową metodę do biblioteki Ruby i uzyskać dostęp do jej prywatnych pól. Mogą istnieć przypadki, w których taki hack może być użyteczny, ale byłoby to naruszenie enkapsulacji. (Nie mam nic przeciwko dodawaniu metod opartych tylko na interfejsie publicznym, ale to nic, czego nie mogą zrobić statycznie wpisane metody rozszerzenia C #).
Możesz dodać nowe pole do obiektu czyjejś klasy, aby przekazać z nim dodatkowe dane. Ale lepszym rozwiązaniem jest po prostu utworzenie nowej struktury lub rozszerzenie oryginalnego typu.
Ogólnie rzecz biorąc, im bardziej uporządkowany kod ma pozostać, tym mniejszą zaletą powinna być możliwość dynamicznej zmiany definicji typów lub przypisywania wartości różnych typów do tej samej zmiennej. Ale wtedy twój kod nie różni się od tego, co można osiągnąć w języku o typie statycznym.
Dynamiczne języki są dobre w cukrze syntaktycznym. Na przykład podczas odczytywania zdekrializowanego obiektu JSON możesz odwoływać się do zagnieżdżonej wartości po prostu jako
obj.data.article[0].content
- znacznie starszego niż powiedziećobj.getJSONObject("data").getJSONArray("article").getJSONObject(0).getString("content")
.Szczególnie programiści Ruby mogą długo mówić o magii, którą można osiągnąć poprzez wdrożenie
method_missing
, która jest metodą pozwalającą na obsługę prób wywołania niezadeklarowanych metod. Na przykład ActiveRecord ORM używa go, aby można było wykonać połączenieUser.find_by_email('[email protected]')
bez deklarowaniafind_by_email
metody. Oczywiście nie jest to nic, czego nie można by było osiągnąćUserRepository.FindBy("email", "[email protected]")
w statycznie pisanym języku, ale nie można zaprzeczyć, że jest porządny.źródło
Wzorzec dynamicznego proxy jest skrótem do implementacji obiektów proxy bez potrzeby posiadania jednej klasy dla każdego typu, który wymaga proxy.
Używając tego
Proxy(someObject)
tworzy nowy obiekt, który zachowuje się tak samo jaksomeObject
. Oczywiście będziesz także chciał w jakiś sposób dodać dodatkową funkcjonalność, ale jest to przydatna baza na początek. W pełnym języku statycznym musisz albo napisać jedną klasę proxy dla typu, który chcesz proxy, lub użyć dynamicznego generowania kodu (co oczywiście znajduje się w standardowej bibliotece wielu języków statycznych, głównie dlatego, że ich projektanci są świadomi problemy, które nie są w stanie tego zrobić).Innym przykładem użycia języków dynamicznych jest tak zwane „łatanie małp”. Pod wieloma względami jest to raczej anty-wzór niż wzór, ale można go używać w użyteczny sposób, jeśli zostanie starannie wykonany. I choć nie ma teoretycznego powodu, dla którego łatanie małp nie mogło zostać zaimplementowane w języku statycznym, nigdy nie widziałem takiego, który faktycznie go ma.
źródło
tak , istnieje wiele wzorców i technik, które są możliwe tylko w dynamicznie pisanym języku.
Patchowanie małp jest techniką polegającą na dodawaniu właściwości lub metod do obiektów lub klas w czasie wykonywania. Ta technika nie jest możliwa w języku o typie statycznym, ponieważ oznacza to, że typów i operacji nie można zweryfikować w czasie kompilacji. Innymi słowy, jeśli język obsługuje łatanie małp, jest to z definicji to język dynamiczny.
Można udowodnić, że jeśli język obsługuje łatanie małp (lub podobne techniki modyfikowania typów w czasie wykonywania), nie można go sprawdzić statycznie. Nie jest to więc ograniczenie w obecnie istniejących językach, ale podstawowe ograniczenie pisania statycznego.
Zatem cytat jest zdecydowanie poprawny - więcej rzeczy jest możliwe w języku dynamicznym niż w języku typowanym statycznie. Z drugiej strony, niektóre rodzaje analiz są możliwe tylko w języku o typie statycznym. Na przykład zawsze wiesz, które operacje są dozwolone na danym typie, co pozwala wykryć nielegalne operacje na typie kompilacji. Taka weryfikacja nie jest możliwa w języku dynamicznym, gdy operacje można dodawać lub usuwać w czasie wykonywania.
Dlatego nie ma oczywistego „najlepszego” konfliktu między językami statycznymi i dynamicznymi. Języki statyczne rezygnują z pewnej mocy w czasie wykonywania w zamian za inną moc w czasie kompilacji, która ich zdaniem zmniejsza liczbę błędów i ułatwia rozwój. Niektórzy uważają, że kompromis jest tego wart, inni nie.
Inne odpowiedzi dowodzą, że równoważność Turinga oznacza, że wszystko, co możliwe w jednym języku, jest możliwe we wszystkich językach. Ale to nie następuje. Aby wesprzeć coś takiego jak łatanie małp w języku statycznym, musisz w zasadzie zaimplementować dynamiczny podjęzyk w języku statycznym. Jest to oczywiście możliwe, ale argumentowałbym, że programujesz wtedy we wbudowanym dynamicznym języku, ponieważ tracisz również statyczne sprawdzanie typów, które istnieją w języku hosta.
C # od wersji 4 obsługuje dynamicznie wpisywane obiekty. Najwyraźniej projektanci języków widzą korzyści z posiadania obu rodzajów pisania. Ale pokazuje również, że nie możesz mieć ciasta i jeść również: kiedy używasz dynamicznych obiektów w C #, zyskujesz zdolność robienia czegoś takiego, jak łatanie małp, ale tracisz również weryfikację typu statycznego dla interakcji z tymi obiektami.
źródło
Tak i nie.
Są sytuacje, w których programista zna typ zmiennej z większą precyzją niż kompilator. Kompilator może wiedzieć, że coś jest Obiektem, ale programista będzie wiedział (ze względu na niezmienniki programu), że w rzeczywistości jest to Łańcuch.
Pokażę kilka przykładów:
Wiem, że
someMap.get(T.class)
to zwróciFunction<T, String>
, ponieważ zbudowałem someMap. Ale Java jest tylko pewna, że mam funkcję.Inny przykład:
Wiem, że data.properties.rowCount będzie prawidłowym odwołaniem i liczbą całkowitą, ponieważ zweryfikowałem dane pod kątem schematu. Gdyby brakowało tego pola, zostałby zgłoszony wyjątek. Ale kompilator wiedziałby tylko, że albo zgłasza wyjątek, albo zwraca jakiś rodzajowy JSONValue.
Inny przykład:
„II6” określa sposób, w jaki dane kodują trzy zmienne. Ponieważ określiłem format, wiem, które typy zostaną zwrócone. Kompilator będzie wiedział tylko, że zwraca krotkę.
Motywem łączącym wszystkie te przykłady jest to, że programista zna typ, ale system typów na poziomie Java nie będzie w stanie tego odzwierciedlić. Kompilator nie zna typów, a zatem język o typie statycznym nie pozwala mi nazywać go, podczas gdy język o typie dynamicznym tak.
Właśnie o to chodzi w pierwotnym cytacie:
Używając pisania dynamicznego, mogę używać najbardziej pochodnego typu, o którym wiem, a nie tylko najbardziej pochodnego, jaki zna mój system typów w moim języku. We wszystkich powyższych przypadkach mam kod, który jest semantycznie poprawny, ale zostanie odrzucony przez statyczny system pisania.
Aby jednak wrócić do pytania:
Dowolny z powyższych przykładów, a nawet dowolny przykład pisania dynamicznego, można wprowadzić do wpisywania statycznego, dodając odpowiednie rzutowania. Jeśli znasz typ, którego nie zna twój kompilator, po prostu powiedz kompilatorowi, przesyłając wartość. Tak więc, na pewnym poziomie, nie będziesz uzyskiwać żadnych dodatkowych wzorów za pomocą dynamicznego pisania. Być może trzeba będzie rzucić więcej, aby uzyskać działanie statycznego kodu.
Zaletą dynamicznego pisania jest to, że możesz po prostu używać tych wzorców, nie martwiąc się faktem, że trudno jest przekonać swój system typów o ich ważności. Nie zmienia dostępnych wzorców, po prostu ułatwia ich implementację, ponieważ nie musisz wymyślać, jak sprawić, by Twój system typów rozpoznał wzorzec lub dodał rzutowania, aby obalić system typów.
źródło
data = parseJSON<SomeSchema>(someJson); print(data.properties.rowCount);
a jeśli nie ma klasy, do której można by dokonać deserializacji, możemy do niej wrócićdata = parseJSON(someJson); print(data["properties.rowCount"]);
- która jest nadal wpisana i wyraża tę samą intencję.data.properties
jest to obiekt i wiedziałem, żedata.properties.rowCount
to liczba całkowita, i mogłem po prostu napisać kod, który ich używał. Twoja propozycjadata["properties.rowCount"]
nie zapewnia tego samego.Oto kilka przykładów z Objective-C (typowanie dynamiczne), które nie są możliwe w C ++ (typowanie statyczne):
Umieszczanie obiektów kilku różnych klas w tym samym pojemniku.
Oczywiście wymaga to kontroli typu środowiska wykonawczego, aby następnie zinterpretować zawartość kontenera, a większość znajomych korzystających z pisania statycznego sprzeciwi się, że nie powinieneś tego robić w pierwszej kolejności. Odkryłem jednak, że poza debatami religijnymi może się to przydać.
Rozszerzanie klasy bez podklas.
W Objective-C możesz zdefiniować nowe funkcje składowe dla istniejących klas, w tym zdefiniowane językowo, takie jak
NSString
. Na przykład możesz dodać metodęstripPrefixIfPresent:
, abyś mógł powiedzieć[@"foo/bar/baz" stripPrefixIfPresent:@"foo/"]
(zwróć uwagę na użycieNSSring
literałów@""
).Korzystanie z obiektowych wywołań zwrotnych.
W językach o typie statycznym, takich jak Java i C ++, musisz dokładać znacznych starań, aby umożliwić bibliotece wywoływanie dowolnego elementu obiektu dostarczonego przez użytkownika. W Javie obejściem jest para interfejs / adapter plus anonimowa klasa, w C ++ to obejście jest zwykle oparte na szablonie, co oznacza, że kod biblioteki musi być narażony na kod użytkownika. W Objective-C po prostu przekazujesz odwołanie do obiektu plus selektor metody do biblioteki, a biblioteka może po prostu i bezpośrednio wywołać wywołanie zwrotne.
źródło
void*
sam w sobie nie jest dynamicznym pisaniem, lecz brakiem pisania. Ale tak, dynamic_cast, tabele wirtualne itp. Sprawiają, że C ++ nie jest typowo statycznie typowany. Czy to złe?void*
na określony typ obiektu. Ten pierwszy powoduje błąd w czasie wykonywania, jeśli się pomyliłeś, a później powoduje niezdefiniowane zachowanie.