V diskusi pod článkem o smalltalkovském doesNotUnderstand a jeho ekvivalentech v jiných jazycích se objevilo pár nepřesností, takže zde je stručně napravím.
Především je zcela zřejmé, že v C++ žádné dynamické volání nikdy implementovat nemůžeme. To by mělo být jasné každému studentovi po prvním semestru C++. Dynamický dispatch vždy vyžaduje podporu překladače a/nebo runtimu.
A teď k Objective-C. Tento jazyk v současné verzi nepoužívá tabulku řetězců se jmény metod. ObjC má odjakživa selektory a ty jsou vnitřně reprezentovány čísly. Zde je vidět souhra překladače a runtimu. Za běhu se používá inline caching. Nový překladač (clang/LLVM) s rozšířeným runtimem má zrychlené volání zpráv. Díky optimalizacím během překladu i za běhu programu je rychlost volání metod (dispatch objektu známých zpráv) zhruba stejně rychlé jako běžné volání v C++ (ještě nedávno bylo podstatně pomalejší, aspoň co se týče starého runtimu a kódu přeloženého pomocí gcc). Podstatně pomalejší samozřejmě bude případ neznámých zpráv, kdy runtime musí vytvářet instanci NSInvocation.
U Objective-C je dynamický dispatch dotažený téměř k dokonalosti (což je záležitost posledních pár měsíců), navíc se (údajně 7×) zrychlil autorelease pool. Nový překladač zjevně provádí mnoho optimalizací, které gcc nikdy nemělo (pochopitelně, měli plné ruce práce s C++).
Jen pro doplneni, toto plati pro vsechny dobre implementovane dynamicke jazyky (jazyky s dynamickym dispatchem - Smalltalk, Java) - nejen pro Obj-C. Ostatni jazyky/VM take neporovnavaji retezce znak pro znaku - klic do tabulky je sice retezec, ale internovany, takze se porovnavaji pointery. Pouziti Inline Caches/Polymorphic Inline Caches jsou samozrejmosti - volani metody je pak zhruba stejne rychle jako v ObjC pripadne C++ (pro rekneme 90% volani)
Pro ty, co neví, co je dynamický dispatch a neznají ObjC, podívejte se na dynamic v C#. Většina lidí si to plete s reflexí. Jde o to, že obj.metoda() (pokud "metoda" neexistuje) zavolá speciální funkci, která dostane název metody jak řetězec (a parametry v poli). V C# se volá TryInvokeMember a název metody se vezme z binderu. C++ takový mechanismus nemá, nemá ani javovský InvocationHandler.
Jo retezec... takze jaky je rozdil mezi volanim neznamych metod a vyvolavani neznamych interfacu, jehoz jmeno se predava jako objekt typeid, ktery zpravidla obsahuje jmeno interface jako text.
V prvnim pripade potrebuju retezex a pole parametru, v druhem pripade retezec, index funkce a pole parametru. Az na ten index je to to same a krom toho interface resolvuju jednou, metodu resolvuju pokazde co ji potrebuju zavolat
Lze si to zawrapovat přes třídu. Pak objekt, který volám má určitě nějaký protokol, který se dá popsat interfacem. Minimálně známe seznam metod, které umí a jaké mají tyto metody parametry. Mohu tedy v místě programu napsat wrapper, který vně vystupuje jako objekt, ale uvnitř všechna volání přepisuje systémem, jak popsal Sten.
Pokud proxy disponuje systémem vyhledávání rozhraní, může síťový objekt identifikovat, vytvořit patřičný wrapper a vrátit jeho instanci jako nalezenou implementaci volaného rozhraní. Volající pak nemá šanci poznat, jestli komunikuje přímo nebo po síti. Syntaxticky se to chová stejně. Prostě má rozhraní, volá jeho metody a získává výsledky.
Ano, znamená to, že v C++ musím znát dopředu všechny objekty vyskytující se v celé aplikaci a v celé síti a nejsem schopen komunikovat s objektem, které jsem při překladu neznal a neměl k němu popis protokolu v podobě rozhraní. Ale objekt může nabízet vícero rozhraní a pak se s ním domluvím i jinak.
Mám pocit, že něco podobného se používá u COM+ a Vzdálené volání procedur ve Windows. Dokonce lze COM+ volat z čistého ANSI C. Tím považuju mýtus o nemožnosti dynamických volání za vyvrácený.
K bodu, který vidíte jako 13 (avšak já jako 14, díky špatné moderaci tohoto fóra)...
Napadlo vás někdy, že by bylo možné ty popisy sdílet? Že jde o jeden soubor, který se nainkluduje do serveru i do klienta? A že nejde o kód (výkonný), ale o deklarace. Je to prostě předpis, jako Bible, nebo jako Ústava, to že jí máte X kopiích neznamená, že je duplicitní. Je to prostě popis protokolu.
K tom dalšímu bodu... Wrapper napíšete buď vy, nebo jej napíše váš překladač. Někdo ho napíše. Buď máte u každé metody uložený textový název a popisy typů, jak jsou uložené parametry a návratová hodnota, nebo vám ten popis tam strčí překladač. Případně můžete wrapery generovat nějakým generátorem kódu. Těch je hafo. Každopádně, ten wraper tam bude vždycky! Pokud tam máte metodu, která obdrží řetězec jako název funkce, tak překladač musí mít ten název někde uložený a musí na základě zprávy (která nemusí zrovna obsahovat ten název! třeba v C++ volání funkce se kóduje jako číselná adresa!) vygenerovat textový řetězec názvu funkce.
V OOP a obecně vám nikdo nezaručuje, že volání metody obj.delejNeco() se implementuje jako zpráva obsahující textový řetězec "delejNeco". Ta zpráva třeba může obsahovat "(018AC78-4587EC-454548...}" jako ID metody a pak tam musí překladač vyrobit, kvůli takové univerzální metodě, nějaký wrapper. A to předpokládá, že chcete v síti posílat pakety s textovým názvem metody. Já třeba něco takového nemusím chtít.
Samozřejmě, že potřebuje, vždycky tam nějaký wrapper je, nějaký serializátor, který volání serializuje do streamu a posílá po síti (a na druhé straně deserializátor). Akorát povrchní programátor zaslepený cool věcma daného jazyka nevidí. Vždycky je to volba mezi jednoduchostí a svobodou.
Já nevím, to opravdu tady někdo věří, že ty vyšší programovací jazyky jsou napsané v nějakém assembleru. Všechny jsou bez výhrad v C, případně v C++ jakož nadstavby C. Každou jednotlivou část toho systému lze samozřejmě použít samostatně, jako třeba schopnost serializaci a deserializaci zpráv při komunikaci na síti. Nebo generátor wrapperů, který tam zaručeně bude.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 37 774×
Přečteno 26 420×
Přečteno 24 932×
Přečteno 21 278×
Přečteno 18 932×