[MEGOLDVA] Dinamikusan megválasztott függvény hívása vagy mi :)

 ( dii | 2009. október 6., kedd - 23:52 )

Még normális címet se tudtam neki adni, nemhogy megguglizni. Kérlek segítsetek benne, hogy merre induljak el!

A következő problémát szeretném C++ (Qt) környezetben megoldani:

Tételezzük fel, hogy van egy osztályom, aminek vannak függvényei, példánkban fuggveny1, fuggveny2, fuggveny3. Egy külső forrásból utasítás (script) érkezik, ami meghívná az objektum függvényét, de ugye nem tudjuk melyik, hanem a scriptben érkezik ez az infó is. Ez alapján kellene ugye a függvényt hívni.

Tenyeres talpas megoldás, hogy a bejövő utasítást megnézem:

if (utasitas == "fuggveny1()") objektum->fuggveny1();
if (utasitas == "fuggveny2()") objektum->fuggveny2();
if (utasitas == "fuggveny3()") objektum->fuggveny3();

Működni működik, de több szempontból se jó ez nekem most. Meg lehet-e azt varázsolni valahogy, hogy az utasitas-ban található stringet hívom meg mint függvényt, tehát kb objektum->utasitas jelleggel tudjam futtatni a dolgot?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Ha mindegyik void fv(void) tipusu, akkor csinalsz egy map-et, amit a stringgel indexelsz, es azon keresztul meghivod fv pointerrel.
Hogy a map-be hogy kerul bele, az mas mas kerdes. Hasznalhatsz valamilyen trukkos makrot ezeknel a fuggvenyeidnel, vagy hasznalhatod az alatta levo OS-be/debuggerbe/egyeb dologba beepitett funkcionalitast. Pl. dll/so betoltesnel is szoktak hasznalni ilyet, ami stringbol (szimbolumnevbol) pointert ad.

--
Always program as if the person who will be maintaining your program is a violent psychopath that knows where you live. - Cotillion, TDWTF

Köszönöm a tippet a map dologgal, utána fogok olvasni. Visszatérési értéke a függvényeknek majdnem hogy mindegy nekem, de paramétereket viszont kaphatnak, úgyhogy nem csak fv(void) típusúak sajnos. Ennek a szimbolumnévből pointer dolognak viszont megpróbálok utána keresni, hátha ilyesmi kell nekem.

http://en.wikipedia.org/wiki/Strategy_pattern

Esetleg, ha van C# féle delegált a C++-ban, akkor az is jó erre.

A különféle paramétertípusokra: készítesz egy class FvArgs {}; osztályt, majd származtatsz gyermekosztályokat az adott függvény adott paraméterezésének megfelelően és ezt adod át. Így egy felületed marad az összes függvénynek.

----------------
Lvl86 Troll

http://hup.pastebin.com/f1af18475

Ha jól értem, Neked fordításidejű algoritmus-választás kell. Ebben az esetben több lehetőséged van:

1, Map(key,member_function) -t csinálsz, majd a key alapján a megfelelő függvényt meghívod. Ennek a hátránya az, hogy nem statikus tagfüggvények esetén mindig el kell készíteni a Map-t, és ott a függvények neveit egyértelműen meg kell adni. Ez csak akkor használható jól, ha az objektum előre ismert ( fix ), vagy a függvények nevei sosem fognak változni ( és bővülni/szűkülni ). Ez utóbbiak miatt hanyagolandó IMHO.

2, Map(key,function object) -t csinálsz, majd a key alapján az objektum operator()-t meghívod. Ez talán jobb, mert futásidőben 1x kell feltölteni a map-t, és minden objektumhoz használható lesz. Itt azonban a függvényobjektumok megvalósítása a korlátozó tényező.

3, Használsz php-t, bash-t. :)

Én nézelődnék a boost-ban, hátha van ilyesmi, én nem annyira ismerem, majd nálam okosabbak megmondják!

Szerintem futásidejű algoritmus-választás kéne neki...

KisKresz

Persze, azt akartam írni, csak reggel volt még...:)

En is a Command pattern mellett tennem le a voksot (ezt a szep elnevezest most olvastam egy konyvben :-), csinal olyan osztalyokat, ahol az operator() felul van biralva, es ezen osztalyokbol kepez map-et.

Igazabol a megvalositas annyira nem gaz, mert az adott osztalynak amugy sem art, ha implemental valami interfeszt.

En meg esetleg arra gondoltam, hogy lehetne az, hogy van egy absztrakt osztaly, ahol le van definialva, hogy az operator() meghiv mondjuk egy execute fuggvenyt, ami pure virtual. A leszarmazottakban pedig gyakorlatilag csak az execute-t kellene megvalositani.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Sima c-ben lehet hasznalni e celra a dlopen(), dlsym() cuccokat. Azaz, ha

if (utasitas == "fuggveny1()") fuggveny1();
if (utasitas == "fuggveny2()") fuggveny2();
if (utasitas == "fuggveny3()") fuggveny3();

eseted lenne, ehelyett egy

void (*fv)(void);
*((void(**)(void))(&fv))=dlsym(handle,utasitas); fv();

hivast lehet csinalni. Kerdes, hogy erre lehet-e c++-os megfelelo"t csinalni, mindenesetre igyis ugyis, az input sanitising-re nem art figyelni.

A rovid valasz: nem, nem lehet.
A hosszu valasz: akkor lehet, ha nem osztaly- vagy namespace-tagokrol beszelunk, es a fuggveny meg van jelolve extern "C" elotaggal. Viszont nem portabilis ez a megoldas. Es bizonyos holdallasokkor nem jo ilyent csinalni. Meg reszleges napfogyatkozaskor. Meg ilyenek.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Szétválasztanám a problémát:
1. string alapján fv hívás.
2. script

1.
Ha C++, akkor a mások által említett map + function-object-ek jöhetnek szóba, bár a paraméterekkel szívás lesz, nem úszod meg az "if-else-if" szerkezeteket.

Ha Qt, akkor QObject::invokeMethod megoldás lehet. Én hasonló esetben a paramétereket QVariant-ba pakoltam, (ha több volt, azt egy QVariantList-be, majd azt egy QVariant-ba). A fv-ek ilyenkor slot-ok, QVariant paraméterrel. A fv-ek maguk mazsolázták ki a paraméterüket. Talán valamivel kevesebb kód kell így, mint "if-else-if" módszerrel...

2.
Ne írj saját scriptnyelvet. Qt alatt használd a QtScript-et, máshol meg valami mást (LUA, Python, stb.). Ezek mindegyikében megoldott egy C++ osztály láthatóvá tétele a scriptből...

http://doc.trolltech.com/4.5/qtscript.html#making-a-c-object-available-to-scripts-written-in-qtscript

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Nem C++ én még mindíg C :)
Gyerekkoromban ezt ugrótáblának hívták. Lehet statikus, azaz fordítási időben leraksz egy kulcs, procedure pointer tömböt az utolsó elem NULL,NULL és akkor egy ciklusban szaladhatsz végig rajta (nincs if, else if, else).
A táblát, persze feltöltheted futási időben is.
Ha erre objektumként kell gondolnom, a problémát a paraméterekkel látom. Én azt csinálnám, hogy mindegyiknek void * adnék oda aztán castolni.

* Én egy indián vagyok. Minden indián hazudik.

"egy ciklusban szaladhatsz végig rajta"
C++-os map annyival jobb ennél, hogy lineáris keresés helyett log-os, plusz azt se neked kell megírni.

"void * adnék oda aztán castolni"
És máris buktad a típusellenőrzést, ami miatt sok hiba csak futásidőben derül ki, holott kiderülhetne fordítási időben is.

Egyébként az általam írt QVariant logikailag ugyanezt csinálja, csak legalább esélyed van az esetleges hiba korai detektálására.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

"C++-os map annyival jobb ennél, hogy lineáris keresés helyett log-os, plusz azt se neked kell megírni." Ez a kód mintegy 5 sor, ahol csak egy egy strcmp kell. Persze ha nagyon sok eljárás az más, de akkor a log helyett hash kell!
"És máris buktad a típusellenőrzést" - tipikus objektum orientált gondolkodás, a C szabadság! - ezért lehet vele oprendszert írni.
A hibát NEM "korán kell detektálni" ki kell zárni! (villamosmérnökként ezt úgy mondják, hogy a zavart/zajt nem szűrni kell, hanem nem szabad generálni.

* Én egy indián vagyok. Minden indián hazudik.

"Ez a kód mintegy 5 sor"
Az meg 1.

" tipikus objektum orientált gondolkodás, a C szabadság! - ezért lehet vele oprendszert írni."
Tán mert a C++ OO nyelv? Sőt erősen típusos OO nyelv. Ami jó.
És persze lehet benne oprendszert írni (szoktak is), mert megadja neked a C alacsony szintű szabadságát is.

"A hibát NEM "korán kell detektálni" ki kell zárni!"
Jaaa, basszus, hogy ezt nekem senki nem mondta... A te kódjaid tehát nem tartalmaznak hibakezelést, ASSERT-et, mert te eleve bugmentesen kódolsz. És persze mindenki más is aki veled dolgozik. Cégetek van már? Lenne pár meló...

Tehát mégegyszer:
A legbiztonságosabb megoldás az lenne, ha minden fv-t saját paramétertípusaival hívnánk. Ez most itt nehézségekbe ütközik.

A következő lépés a QVariant (vagy hasonló) megoldás, ahol legalább ellenőrizni tudod, hogy tényleg olyan típusokat kapsz-e mint amit vársz.

Végül marad a void*, meg a remény.

Egyébként nem akarlak én meggyőzni, használj C-t. De a kérdés C++-ra vonatkozott, és C++-ban van a C-s megoldásnál biztonságosabb, esetleg hatékonyabb módszer. Az ember akkor menjen alacsony szintre ha muszáj. C kódot sem rakunk tele asm betétekkel csak azért mert lehet...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Megirhatod igy, de akkor csak gcc -vel forgathatod, g++-szal nem, mert az nagyon randa dolgokat fog csinalni.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Minek void * ezt nem ertem. Elvben ugyanazokbol a kiindulo adatokbol dolgozo fuggvenyekrol beszelunk, tehat siman lehet specifikus fuggvenypointereket csinalni (mittomen, int (*fpointer)(int, char *) vagy akarmi).
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

"Visszatérési értéke a függvényeknek majdnem hogy mindegy nekem, de paramétereket viszont kaphatnak"

Ebből én arra következtettem, hogy a paraméterek száma és típusa nem egyezik meg a fv-ek között...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

A void * a bármi. Régi jó C trükk. Írtál már C -ben interruptot kezelő device drivert? Vagy csak nyúlj le a windows API -ba!
Csak egy példa. Kedvenc windows IO API a IOCompletionQueue és az overlap struktúra, a megvalósításokban pedig a struktúra egy overlap struktúrával nyit, viszont mőgőtte ott van ami a működéshez kell, így az IO kezelő API egy overlap struktúrát kap, míg a handler mit írsz mindent ami neki kell.
Persze lehet minden strickt és akkor jól alszol, viszont lassú és túl bonyolult, átláthatatlan.

* Én egy indián vagyok. Minden indián hazudik.

Miert lenne bonyolult? Ertelmes nevet kell adni mindennek, es akkor nem bonyolult. Es raadasul ot ev utan is elso ranezesre megmondom neked egy jol definialt callback tipusrol, hogy az mit csinal, mit var, es mit ad vissza. Megneznem, hogy egy void * cuccnal hol van meg ez a lehetoseg.

En nem mondom, hogy a te modszered nem mukodik, csak azt mondom, hogy van mas is, ami lehet, hogy jobb. Aztan szived joga, hogy mit valasztasz.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Plusz ezeknek a típusos dolgoknak a nagy része futásidőben és memóriafoglalásban is ingyen van, sőt nem egy esetben a fordító hatékonyabb kódot tud generálni mint a C-s megoldással, egyszerűen azért, mert több információ áll a rendelkezésére.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Mielőtt a fejemet vennétek csak Lafore C++ könyvének a bevezetője jut az eszembe (szabadon frodítva): "Én még akkor kezdtem a programozást amikor a 4 Kbájt operatív memória luxus volt ..." Viszont, akkor még nem voltak ablakos, grafikus GUI -k ! Amikor ilyen szinten kell programozni akkor én is OO programot írok, de még annál is - templatek, amiből egy adatbázis "dictionary" alapján, a keretrendszer egy "wizard" segítségével működő programot generál, ezt lehet tovább finomítani. Itt már fel sem merül, hogy mondjuk megnézzem a "közbülső assembly forrást" mint azt a mai összes és C/C++ fordító azt lehetővé teszi.
Midezek fundatmentumában azonban ilyen, az általam leírott dolgok fekszenek.

UI: Amit a srác elővezetett az egy alapvető programozási eszköz, én sokszor alkalmaztam ilyen struktúrát, így a forráskód "könyvtáram" oszlopos része. Épült belőle állapot automata, egyszerű értelmező stb.
"assert" és hasonlókat nem használok, minden programom alapjában van a futtató rendszer lehetőségeire támaszkodó naplózás - log. A log -nak természetesen be lehet állítani a "severity" szintjét.

* Én egy indián vagyok. Minden indián hazudik.

Az, hogy most dogivel van memoria, az nem azt jelenti, hogy azt egyetlen alkalmazas segitsegevel tele kell fosni. Torekedni kell arra, hogy feleslegesen ne hasznaljunk tomentelen memoriat, es foleg ne "folyassuk" el a memoria nagy reszet a sajat trehanysagunk miatt. Van, ahol a kevesebb neha tobb.
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Köszönöm szépen az érdekes és hasznos ötleteket. Tr3w megfejtése tűnik a legjobbnak, de több olyan számomra ismeretlen terület szóba került, aminek biztos utána fogok nézni. Neki is állok megdolgozni a problémát...

Mindenkinek köszönöm még egyszer.