Významnou vlastností některých dynamických jazyků je schopnost flexibilně reagovat na volání metody, která u daného objektu neexistuje. Jak známo, Smalltalk má zprávu doesNotUnderstand, kterou objekt dostane, pokud neumí obsloužit jinou zprávu. Předaný objekt aMessage rozumí zprávám selector (vrací onu neznámou zprávu) a arguments (argumenty volání). Tímto způsobem je možné implementovat jakousi záchrannou síť, která se aktivuje v případě zaslání zprávy neznámé v době překladu.
Narozdíl od staticky typovaných jazyků s prostou introspekcí (Java a C# před verzí 4) je tak možné implementovat dynamicky rozšiřitelné objekty. Objective-C mělo kdysi stejný systém jako Smalltalk, dnešní verze má methodSignatureForSelector a forwardInvocation, což je prakticky to samé v bledě modrém. Neznámá zpráva je zabalena do NSInvocation a předána forwardInvocation. Standardní implementace jednoduše vyhodí výjimku („selector not recognized“), ve speciálních případech lze ale zprávu přesměrovat nebo jinak ošetřit a výjimce se tak vyhnout. Pro některé speciální případy má nová verze runtimu ještě forwardingTargetForSelector, umožňující zprávu přesměrovat bez vysoké režie forwardInvocation a NSInvocation.
V dávných dobách OpenStepu byly na této technice založené distribuované objekty (něco jako pozdější DCOM, ale „light weight“). Své „doesNotUnderstand“ dostalo i C#. Ve čtvrté verzi je možné u podtříd DynamicObject předefinovat metodu TryInvokeMember (a některé další, např. pro přístup k vlastnostem). Tak lze implementovat plně dynamický objekt, jak jej známe například z Javascriptu. S použitím dynamic (něco jako var, ale během překladu nedochází k odvození typu) můžeme deklarovat dynamický objekt (např. dynamic obj) a pak použít kdekoliv v kódu obj.Neco(), i když žádná taková metoda neexistuje (překladač si nebude stěžovat a za běhu se zavolá TryInvokeMember).
Je zřejmé, že takovéto odchytávání zpráv (resp. volání metod v C#) s sebou nese značnou režii a většinou není potřebné. Typickým použitím jsou právě distribuované objekty, kde režie navíc nevadí (následná síťová komunikace je řádově horší) a vyhneme se elegantně stubům a skeletonům (pokud nevíte, o čem mluvím, přečtěte si něco o RMI v Javě).
Umim to v C++. Ikdyz ne takhle primo ale pres interfacy, vcetne jejich forwardovani. Postup je ten, ze se osloveny objekt pozada o rozhrani, ktere neumi, objekt obdrzi event a jeho ukolem je vyhledat podobjekt, delegata, nebo kohokoliv, kdo to rozhrani implementuje a tomu pozadavek predat. Zapis:
obj.getIfc{INeco}().neco()
{} jsou spicate zavorky (sablony)
@5 No to jste jen nepochopil o čem jsem psal. Ta šablona je tam jen proto, abych nemusel psáte typeid(INeco). Jinak je to celé založené na typeid a dynamic_cast.
Zdrojáky jsou zde: http://pastebin.com/7FxsNiha
Můžete je volně použít.
Stejně jako v jiných jazycích musíte znát minimálně jméno metody a její parametry, než ji zavoláte, i tady ji musíte znát. Jenže v C++ ji musíte nadeklarovat, přesněji schovat do nějakého interface. To není problém, protože obě strany, tedy jak ta strana, co metodu volá, tak ta strana, která metodu implementuje, mohou deklaraci interface naincludovat. Ta strana mezitím jí znát nepotřebuje, a v tom je celé kouzlo.
V interface nemusíte mít jednu funkci, ale třeba několik, které spolu souvisí. Pak stačí si z objektu interface vytáhnout a zavolat kýženou funkci. To "vytažení" interface je to hlavní, protože tenhle požadavek znamená, že volaný musí někde opatřit implementaci toho interface a to právě přes metodu, která se u mě jmenuje "proxyInterface()", a která se dá předefinovat přesně stejným způsobem, jako metody "doesNotUnderstand()". Jakmile mám interface vytažený, už komunikuji přímo s tím, kdo metodu umí a není problém ji zavolat.
Asi nejblíž k tomu má COM+ se svým QueryInterface. Problém je, že Microsoft si sám zůžil prostor pravidly tak, že interface musí být symetrický a reflexivní a transitivní. To znemožňuje proxování interfaců. Nicméně, mají tam ještě jednu metodu a to QueryService a ta funguje podobně bez těhle pravidel. Ale už není tak standardní.
V C++ se dá dynamické zpracování napsat taky. Jen to pro něho není přirozené. V runtime vše lítá jako pointery, takže vlastně případné dynamický dispatching zprávy dopadne tak, že se napíše poloviční interpretr dynamického jazyka.
Což není nic složitého. Ve všech dynamických jazycích je zpracování zprávy hašovací tabulka stringů, která převede název zprávy na pointer metody, která jí zpracuje. Pokud se nenajde daný řetězec, následuje dynamický dispatching.
Jeden rozdíl ale je. C++ nezná typy v programu. Zná pouze tytpy v modulu, proto použití dynamic_cast() a podobně je celkem k ničemu, pokud nevíte, kde můžete narazit. Kromě toho je dynamic_cast() velmi pomalé. Tento operátor hledá pomocí strcmp() funkce řetězec a to je velmi pomalá akce, která nemůže ani pomýšlet na rychlost danoou hašovacími tabulkami.
Miloslav Ponkrác
@7 Pane Ponkrác. Programuju 2 desetiletí a nemusíte mě to učit. Znám naprosto přesně, co se děje při dynamic_cast. I co se děje ve Visual Studio. Předem tvrdím, že nelze paušalizovat, že dynamic_cast je pomalý. Toto platí jen pro Microsoftí implementaci. Nadruhou stranu, není to tak hrozný.
Dynamicky typované jazyky hledají funkce v hashovacích tabulkách, někteří prý dokonce ne (@8), pak to asi nějak čaruji. Co já vím, tak třeba python má prostě dictionary, což není nic jiného, než prostě asociativní pole, a teď neřeším, jak je implementované, ale v průměru nebude mít o nic lepší složitost než log N (N je počet funkcí na objektu). Velmi podobné to bude třeba u javascriptu. Existence metody "doesNotUnderstand" je tedy jen logické rozšíření tohoto dispatchingu.
A teď si to vemte, že v dynamicky typovaném jazyce hledáte název funkce, ať už jde o textový řetězec nebo nějaký ID, GUID, whatever. A porovnejte to s hledáním rozhraní v C++ ať už jde o textový řetězec nebo nějaké ID, GUID, whatever, který toto rozhraní identifikuje. Najděte pět rozdílů!
Já bych viděl jeden podstatný. A ten hraje do karet C++. Zatímco pokud zavolám na neznámém objektu nějakou metodu, bude se hledat. Pravděpodobně se bude hledat pokaždé co jí budete volat. Možná, že tam bude nějaká cache, která přístup zoptimalizuje, ale moc bych na to nesázel. No a v C++ si nejprve necháte vyhledat interface, a pak už vesele voláte metody na tom interface, aniž byste musel rozhraní a funkce znova a znova hledat.
Virtuální funkce v C++ mají díky povinné deklaraci všeho jednu převratnou výhodu a to tu, že každá funkce může mít ID představující index do tabulky funkcí, takže není třeba již nic hledat, složitost je 1. Pouze složitost hledání rozhraní bude zhruba log N.
Ovšem ne u Microsoft C++. Microsoft by samozřejmě mohl lépe organizovat RTTI data, mohly by vedle názvů tříd používat i 4bajtové hashe, hash tabulky, a podobně, možná by byl rychlejší, než to, jak to dělá teď ... projíždí obyčejné pole a každou položku vyhodnocuje porovnáváním řetězců. Otázkou je, o kolik procent by se to zrychlilo celkově. Nicméně zhlediska jazyka, nic lepšího než dynamic_cast nemáme, a ani vlastně nepotřebujeme. K dynamickému typování naprosto dostačuje.
@9 no ono se to nedá spustit ani s těmi hlavičkovými soubory. Je to plné deklarací. Interface se musí podědit nějakou třídou. Příklady vám teď tady dávat nebudu, nemám na to čas. Časem možná dofinalizuju celou knihovnu (LightSpeed) a dám jí někam na download. Postoval jsem to pro ty, kteří umí číst v C++ zdrojáku. Jsou tam dvě zajímavé metody getIfc a proxyInterface.
Když mám objekt Test např. s metodou helloWorld(), můžu napsat (v C#):
var obj = new Test(); obj.helloWorld();
To se přeloží se statickou vazbou a překladač kontroluje existenci metody a typy. U distribuovaných objektů napíšu:
var obj = ObjectBroker.createObject("Test"); obj.helloWorld();
V tomto případě bude obj instance nějaké proxy třídy a instance třídy Test je fyzicky někde jinde (typicky na jiném počítači). Podstatné je, že zbytek kódu se nemění a ani nepotřebuju žádný interface (většinou se používá rozhraní kvůli typové kontrole, ale není nutné).
V ObjC bych napsal v obou případech [obj helloWorld], ve Smalltalku dtto (bez hranatých závorek). U vzdáleného objektu se zpráva zabalí (jméno metody se vezme z NSStringFromSelector) a pošle přes síť. V C# se zase použije InvokeMemberBinder.Name.
V C++ to takto elegantně nejde (ani v Javě).
10: Pane Nováčisko, odpusťte si prosím ty komentáře, že Vás nemusím nic učit.
Programuji ještě déle, než Vy.
Pokud nechápete rozdíl zpracování zpráv ve statických a dynamických jazycích, sorry. Ale možností zpracování názvů jako je v dynamických jazycích prostě v C++ na standardních třídách C++ nedosáhnete, kdybyste se rozkrájel.
Možná bych Vás mohl poučit, že C++ nemá v programu typy, typy má pouze modul. Tedy určení typu v programu není a nemůže být z principu C++ jednoznačné.
Proto se C++ standard ani Microsoft do lepšího RTTI, nebo optimalizovanějího RTTI nehrne. Protože jsou v tom háčky typu, že typ IInterface může v modulu A být úplně něčím jiným, než v modulu B.
Takže jak standard C++, tak Microsoft, tak další mají důvod mírnit lidi v používání RTTI. Vzhledem k tomu, že typu jsou vázány na modul nikoli na program, se v tom dá udělat pár ošklivých kopanců a nemá to rozumné řešení.
@11 a ještě k těm interfacům. Kdo používá jen finální objekty v C++ neumí C++. K maximálnímu využití OOP je potřeba všechno řešit přes interface. Interface je popis protokolu jak objekty komunikují. To co mi chybí v dynamicky typovaných jazycích. Interface nejen udává pravidla, ale krásně dokumentuje každé spojení mezi dvěmi černými krabičkami.
@13
"Pokud nechápete rozdíl zpracování zpráv ve statických a dynamických jazycích, sorry. Ale možností zpracování názvů jako je v dynamických jazycích prostě v C++ na standardních třídách C++ nedosáhnete, kdybyste se rozkrájel."
Evidentně jste za tu dobu neobjevil podobnost. Váš boj.
"Možná bych Vás mohl poučit, že C++ nemá v programu typy, typy má pouze modul. Tedy určení typu v programu není a nemůže být z principu C++ jednoznačné."
Blábol
"Proto se C++ standard ani Microsoft do lepšího RTTI, nebo optimalizovanějího RTTI nehrne. Protože jsou v tom háčky typu, že typ IInterface může v modulu A být úplně něčím jiným, než v modulu B."
Blábol
"Takže jak standard C++, tak Microsoft, tak další mají důvod mírnit lidi v používání RTTI. Vzhledem k tomu, že typu jsou vázány na modul nikoli na program, se v tom dá udělat pár ošklivých kopanců a nemá to rozumné řešení"
Blábol
Ono v jiných jazycích ani moduly nejsou, takže proto to jsou všechno bláboly. Typovou komaptibilitu si samozřejmě v rámci třeba distribuovaného vyvýjeného systému musíte zajistit sám, to za vás neudělá nikdo, ani dynamicky typovaný jazyk. Maximálně tak zkrz knihovny, které se dají napsat i v C++.
Autor se zabývá vývojem kompilátorů a knihoven pro objektově-orientované programovací jazyky.
Přečteno 36 200×
Přečteno 25 361×
Přečteno 23 795×
Přečteno 20 177×
Přečteno 17 872×