C++ szabvány

Mutatók tagfüggvényekre

A “pointer-to-member-function” típusa különbözik a “pointer-to-function”-től?

Igen.

Nézzük a következő függvényt:

int f(char a, float b);

A függvény típusa attól függően különbözik, hogy ez egy közönséges függvény vagy valamilyen osztály nemstatic tagfüggvénye:

Megjegyezzük: ha ez egy class Fred Fred static tagfüggvénye, akkor a típusa ugyanaz, mintha egy közönséges függvény lenne:”int (*)(char,float)“.

Hogyan adhatok át egy tagfüggvényre mutató mutatót egy jelkezelőnek, X esemény visszahívásának, egy szálat/feladatot indító rendszerhívásnak stb.

Ne tedd.

Mert egy tagfüggvény értelmetlen egy objektum nélkül, amin meghívhatod, ezt nem teheted meg közvetlenül (ha az X WindowSystem-et C++-ban írnák újra, valószínűleg objektumokra való hivatkozásokat adnának át, nem csak függvényekre mutatót;természetesen az objektumok megtestesítenék a kívánt függvényt és valószínűleg egy csomó mást is).

A meglévő szoftverek javításaként használj egy felső szintű (nem tag) függvényt wrapper-ként, amely egy más technikával kapott objektumot vesz át. A meghívott rutintól függően ez a “más technika” lehet triviális, vagy igényelhet egy kis munkát a részedről. A rendszerhívás, amely elindít egy szálat, például megkövetelheti, hogy átadjon egy függvénymutatót egy void* mellett, így átadhatja az objektummutatót a void*-ben. Sok valós idejű operációs rendszer valami hasonlót csinál az új feladatot indító függvényhez. Legrosszabb esetben az objektummutatót egy globális változóban tárolhatod; erre a Unix jelkezelők esetében lehet szükség (de a globális változók általában nem kívánatosak). Mindenesetre a legfelső szintű függvény meghívná a kívánt tagfüggvényt az objektumon.

Itt egy példa a legrosszabb esetre (globális használatával). Tegyük fel, hogy meg akarjuk hívni a Fred::memberFn() megszakításon:

Megjegyzés: A static tagfüggvények nem igényelnek tényleges objektumot a meghíváshoz, a sopointer-to-static-tagfüggvények általában típus-kompatibilisek a hagyományos pointer-to-függvényekkel. Azonban,bár valószínűleg a legtöbb fordítóprogramon működik, valójában egy extern "C" nem-tagfüggvénynek kellene lennie ahhoz, hogy helyes legyen, mivel a “C linkage” nem csak olyan dolgokra terjed ki, mint a névkeverés, hanem a hívási konvenciókra is, amelyek eltérőek lehetnek a C és a C++ között.

Miért kapok folyton fordítási hibákat (type mismatch), amikor megpróbálok egy tagfüggvényt megszakításkiszolgáló rutinként használni?

Ez az előző két kérdés speciális esete, ezért először olvassa el az előző két választ.

A nemstatic tagfüggvényeknek van egy rejtett paraméterük, amely megfelel a this mutatónak. A this mutató az objektum példányadataira mutat. A rendszerben lévő megszakítási hardver/firmware nem képes athis mutató argumentumának megadására. “Normál” függvényeket (nem osztálytagokat) vagy static tagfüggvényeket kell megszakításkiszolgáló rutinokként használni.

Egy lehetséges megoldás, hogy egy static tagot használunk megszakításkiszolgáló rutinként, és ez a függvény keres valahol, hogy megtalálja a megszakításkor meghívandó példány/tag párt. Így a hatás az, hogy egy tagfüggvényt hív megszakításkor, de technikai okokból először egy köztes függvényt kell meghívni.

Miért van gondom egy C++ függvény címének átvételével?

Rövid válasz: Ha egy pointer-to-functionba próbálod tárolni (vagy átadni), akkor ez a probléma -ez az előző GYIK következménye.

Hosszú válasz: A C++-ban a tagfüggvényeknek van egy implicit paramétere, amely az objektumra mutat (a this pointerinside the member function). A normál C függvényekre úgy gondolhatunk, hogy a tagfüggvényektől eltérő hívási konvencióval rendelkeznek, így a mutatóik típusai (pointer-to-member-function vs pointer-to-function) különbözőek és inkompatibilisek. A C++ bevezet egy új típusú mutatót, az úgynevezett pointer-to-membert, amelyet csak egy objektum megadásával lehet meghívni.

FIGYELEM: ne próbáljon meg egy pointer-to-member-funkciót pointer-to-funkcióvá “castolni”; az eredmény meghatározhatatlan és valószínűleg katasztrofális. Pl. egy pointer-to-member-function-nak nem szükséges tartalmaznia a megfelelő függvény gépi címét. Ahogy az utolsó példában is elhangzott, ha van egy mutatója egy normál C függvényre, használjon vagy atop-szintű (nem-tag) függvényt, vagy egy static (osztály) tagfüggvényt.

Hogyan kerülhetem el a szintaktikai hibákat a tagokra való mutatók létrehozásakor?

Használjon egy typedef.

Igen, persze, tudom: maga más. Te okos vagy. Meg tudod csinálni ezt a dolgot typedef nélkül is. Sóhaj. Sok e-mailt kaptam olyan emberektől, akik hozzád hasonlóan nem voltak hajlandóak megfogadni a GYIK egyszerű tanácsait. Órákat és órákat vesztegettek el az idejükből, amikor 10 másodpercnyi typedef leegyszerűsítette volna az életüket. Ráadásul, nézz szembe a tényekkel, nem olyan kódot írsz, amit csak te olvashatsz; remélhetőleg olyan kódot írsz, amit mások is el tudnak majd olvasni – amikor fáradtak – amikor nekik is megvan a saját határidőjük és a saját kihívásaik. Miért nehezítenéd meg tehát szándékosan magad és mások életét? Légy okos: használj typedef.

Itt egy mintaosztály:

class Fred {public: int f(char x, float y); int g(char x, float y); int h(char x, float y); int i(char x, float y); // ...};

A tipedef triviális:

typedef int (Fred::*FredMemFn)(char x, float y); // Please do this!

Ez az! FredMemFn a típus neve, és egy ilyen típusú mutató a Fred bármelyik tagjára mutat, amelyik (char,float)-t vesz fel, mint például a Fred f, g, h és i.

Ezután triviális olyan tag-funkciómutatót deklarálni:

int main(){ FredMemFn p = &Fred::f; // ...}

És triviális olyan függvényeket deklarálni, amelyek tag-funkciómutatót fogadnak:

void userCode(FredMemFn p){ /*...*/ }

És triviális olyan függvényeket deklarálni, amelyek tag-funkciómutatót adnak vissza:

FredMemFn userCode(){ /*...*/ }

Szóval kérem, használjon typedef. Vagy ezt, vagy ne küldjön nekem e-mailt a tagfüggvénymutatókkal kapcsolatos problémáiról!

Hogyan kerülhetem el a szintaktikai hibákat, amikor egy tagfüggvényt hívok meg egy pointer-to-member-function segítségével?

Ha olyan fordítóhoz és szabványkönyvtárhoz fér hozzá, amely megvalósítja a készülő C++17 szabvány megfelelő részeit, használjon std::invoke. Ellenkező esetben használjon #define makrót.

Kérem.

Kérem szépen.

Túl sok e-mailt kapok zavarodott emberektől, akik nem voltak hajlandók megfogadni ezt a tanácsot. Pedig olyan egyszerű. Tudom, nincs szükségedstd::invoke vagy makróra, és a szakértő, akivel beszéltél, meg tudja csinálni egyik nélkül is, de kérlek, ne hagyd, hogy az egód az útjába álljon annak, ami fontos: a pénznek. Más programozóknak kell majd olvasni/karbantartani a kódodat. Igen, tudom: okosabb vagy, mint mindenki más; rendben. És fantasztikus vagy; rendben. De ne növeld a kódodat feleslegesen bonyolulttá.

A std::invoke használata triviális. Megjegyzés: FredMemFn egy typedef egy pointer-to-membertype:

Ha nem tudod használni a std::invoke-et, csökkentsd a karbantartási költségeket azzal, hogy paradox módon ebben a konkrét esetben egy #define makrót használsz.

(Általában nem szeretem a #define makrókat, de a pointers tomembers esetén érdemes használni őket, mert javítják az ilyen jellegű kód olvashatóságát és írhatóságát.)

A makró triviális:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

A makró használata szintén triviális. Megjegyzés: FredMemFn egy typedef egy pointer-to-membertype:

Az ok, amiért a std::invoke vagy ez a makró jó ötlet, az az, hogy a tagfüggvények meghívása gyakran sokkal összetettebb, mint az imént megadott egyszerű példa. A különbség az olvashatóság és az írhatóság szempontjából jelentős.comp.lang.c++-nek száz és száz hozzászólást kellett elviselnie zavarodott programozóktól, akik nem igazán értették a szintaxist. Szinte az összes ilyen hiba eltűnt volna, ha a std::invoke vagy a fenti makrót használják.

Megjegyzés: A #define makrók 4 különböző módon gonoszak: gonosz#1,gonosz#2, gonosz#3 és gonosz#4. De ettől még néha hasznosak. Használatuk után azonban még mindig érezned kell egy halvány szégyenérzetet.

Hogyan hozhatok létre és használhatok egy pointer-to-member-function tömböt?

Használd mind a typedef, mind a std::invoke vagy a korábban leírt #define makrót, és 90%-ban kész vagy.

1. lépés: hozz létre egy typedef:

2. lépés: hozz létre egy #define makrót, ha nincs std::invoke:

#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))

Most a pointer-to-member-funkciók tömbje egyszerű:

FredMemFn a = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };

És az egyik tagfüggvény mutatójának használata is egyszerű:

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: std::invoke(a, fred, 'x', 3.14);}

vagy ha nincs std::invoke,

void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: CALL_MEMBER_FN(fred, a) ('x', 3.14);}

Megjegyzem: a #define makrók 4 különböző módon gonoszak: gonosz#1,gonosz#2, gonosz#3 és gonosz#4. De ettől még néha hasznosak. Szégyelld magad, érezd magad bűnösnek, de ha egy olyan gonosz konstrukció, mint a makró, javítja a szoftveredet, használd.

Hogyan deklaráljak egy olyan pointer-to-member-function-t, amely egy const tagfüggvényre mutat?

Rövid válasz: adj egy const-t a ) jobb oldalára, amikor egy typedef-t használsz a member-function-pointertype deklarálására.

Tegyük fel például, hogy olyan tag-függvényre mutató mutatót akarsz, amely Fred::f, Fred::g vagy Fred::h-ra mutat:

class Fred {public: int f(int i) const; int g(int i) const; int h(int j) const; // ...};

Aztán amikor egy typedef-t használsz a tag-függvény-mutató típus deklarálására, így kell kinéznie:

Ez az!

Ezután a tag-függvénymutatókat a szokásos módon deklarálhatod/átadhatod/visszaadhatod:

Mi a különbség a .* és a ->* operátorok között?

Ezt nem kell megértened, ha std::invoke vagy makrót használsz a tag-függvénymutató hívásokhoz. Ohyea, kérlek, használj std::invoke vagy makrót ebben az esetben. És említettem már, hogy ebben az esetben std::invoke vagy makrót kell használnod?!!!

Például:

De kérlek, fontold meg a std::invoke vagy makró használatát helyette:

void sample(Fred x, Fred& y, Fred* z, FredMemFn func){ std::invoke(func, x, 42, 3.14); std::invoke(func, y, 42, 3.14); std::invoke(func, *z, 42, 3.14);}

vagy

Mint korábban tárgyaltuk, a valós meghívások gyakran sokkal bonyolultabbak, mint az itt szereplő egyszerűek, így a std::invoke vagy makró használata általában javítja a kódod írhatóságát és olvashatóságát.

Konvertálhatok egy tagfüggvényre mutató mutatót void*-ba?

Nem!

Technikai részletek: A tagfüggvényekre mutató mutatók és az adatokra mutató mutatók nem feltétlenül ugyanúgy reprezentálódnak. Egy tagfüggvényre mutató mutató lehet inkább egy adatstruktúra, mint egy egyszerű mutató. Gondolj bele: ha egy virtuális függvényre mutat, akkor lehet, hogy valójában nem egy statikusan feloldható kódhalomra mutat, tehát lehet, hogy nem is egy normál cím – lehet, hogy valamilyen másfajta adatstruktúra.

Kérlek, ne írj nekem e-mailt, ha a fentiek úgy tűnik, hogy működnek az adott fordító adott verzióján az adott operációs rendszeren. Nem érdekel. Ez illegális, pont.

Konvertálhatok egy pointer-to-function-t void*-ba?

Nem!

Technikai részletek: void* A mutatók adatokra mutatók, a függvénymutatók pedig függvényekre mutatnak. A nyelv nem követeli meg, hogy a függvények és az adatok ugyanabban a címtartományban legyenek, így, példaként és nem korlátozásként, olyan architektúrákon, ahol ezek különböző címtartományokban vannak, a két különböző típusú mutató nem lesz összehasonlítható.

Kérlek, ne írj nekem e-mailt, ha a fentiek úgy tűnik, hogy az adott fordító adott verzióján az adott operációs rendszeren működnek. Nem érdekel. Ez illegális, és pont.

Az olyanra van szükségem, mint a függvénymutatók, de nagyobb rugalmassággal és/vagy szálbiztonsággal; van más megoldás?

Funkcióoidot használj.

Mi a fene az a funkcióoid, és miért használnék ilyet?

A funkcióoidok a függvények szteroidjai. A funkcionoidok szigorúan erősebbek, mint a függvények, és ez a többleterő megoldja a függvénymutatók használatakor jellemzően felmerülő kihívások egy részét (nem mindegyikét).

Munkáljunk ki egy példát, amely a függvénymutatók hagyományos használatát mutatja be, majd ezt a példát lefordítjuk a funkcionoidokra. A hagyományos függvénymutató-ötlet az, hogy van egy csomó kompatibilis függvényünk:

int funct1( /*...params...*/ ) { /*...code...*/ }int funct2( /*...params...*/ ) { /*...code...*/ }int funct3( /*...params...*/ ) { /*...code...*/ }

Azután ezeket függvénymutatókkal érjük el:

typedef int(*FunctPtr)( /*...params...*/ );void myCode(FunctPtr f){ // ... f( /*...args-go-here...*/ ); // ...}

Egyszer az emberek létrehoznak egy tömböt ezekből a függvénymutatókból:

FunctPtr array;array = funct1;array = funct1;array = funct3;array = funct2;// ...

Ez esetben a tömbhöz való hozzáféréssel hívják meg a függvényt:

array( /*...args-go-here...*/ );

A functionoidoknál először létrehozunk egy alaposztályt egy tisztán virtuális metódussal:

Ezután három függvény helyett három származtatott osztályt hozunk létre:

Ezután a függvénymutató átadása helyett egy Funct*-t adunk át. Létrehozok egy typedef nevű FunctPtr pusztán azért, hogy a kód többi része hasonló legyen a régimódi megközelítéshez:

typedef Funct* FunctPtr;void myCode(FunctPtr f){ // ... f->doit( /*...args-go-here...*/ ); // ...}

Majdnem ugyanígy létrehozhatsz belőlük egy tömböt:

Ez adja az első utalást arra, hogy a functionoidok szigorúan erősebbek, mint a function-pointerek: az a tény, hogy a functionoid megközelítésnek vannak olyan argumentumai, amelyeket átadhatunk a ctoroknak (fentebb …ctor-args… néven látható), míg afunction-pointeres változatnak nincsenek. Gondoljunk a functionoid objektumra úgy, mint egy fagyasztva szárított függvényhívásra (a hangsúly a hívás szón van). A függvényre mutató mutatóval ellentétben a functionoid (fogalmilag) egy részben hívott függvényre mutató mutató. Képzeljünk el egy olyan technológiát, amely lehetővé teszi, hogy átadjunk néhány, de nem minden argumentumot egy függvénynek, majd lehetővé teszi, hogy lefagyasszuk ezt a (részben befejezett) hívást. Tegyük fel, hogy ez a technológia valamiféle mágikus mutatót ad vissza a részben befejezett függvényhívásra. Aztán később átadod a maradék argumentumokat ezzel a mutatóval, és a rendszer varázslatos módon elveszi az eredeti (fagyasztva szárított) argumentumaidat, kombinálja őket minden olyan helyi változóval, amelyet a funkció a fagyasztás előtt kiszámított, mindezt kombinálja az újonnan átadott argumentumokkal, és folytatja a funkció végrehajtását ott, ahol a fagyasztáskor abbahagyta. Ez talán sci-finek hangzik, de a koncepció szerint ez az, amit a functionoidok lehetővé tesznek. Ráadásul lehetővé teszik, hogy ismételten “befejezzük” a fagyasztva szárított függvényt különböző “maradék paraméterekkel”, olyan gyakran, amilyen gyakran csak akarjuk. Plusz lehetővé teszik (nem követelik meg), hogy megváltoztasd a fagyasztva szárított állapotot, amikor meghívják, ami azt jelenti, hogy a functionoidok képesek megjegyezni az információkat egyik hívástól a másikig.

Lépjünk vissza a földre, és dolgozzunk ki néhány példát, hogy elmagyarázzuk, mit jelent valójában ez a sok hókuszpókusz.

Tegyük fel, hogy az eredeti függvények (a régimódi function-pointer stílusban) kissé eltérő paramétereket kaptak.

int funct1(int x, float y){ /*...code...*/ }int funct2(int x, const std::string& y, int z){ /*...code...*/ }int funct3(int x, const std::vector<double>& y){ /*...code...*/ }

Ha a paraméterek különbözőek, a régimódi függvénymutatós megközelítés nehezen használható, mivel a hívó nem tudja, hogy milyen paramétereket kell átadni (a hívó csupán egy mutatót kap a függvényre, nem pedig a függvény nevét vagy,ha a paraméterek különbözőek, a paraméterek számát és típusát) (ne írj nekem erről e-mailt; igenmeg lehet csinálni, de fejre kell állni és rendetlen dolgokat kell csinálni; de ne írj nekem erről – használj inkább functionoidokat).

A functionoidokkal a helyzet, legalábbis néha, sokkal jobb. Mivel egy functionoidot úgy lehet elképzelni, mint egy fagyasztva szárított függvényhívást, csak fogd a nem közös args-okat, mint amilyeneket y és/vagy z-nek neveztem, és tedd őketargs-okká a megfelelő ctorokhoz. A közös args-t (ebben az esetben a int hívott x) is átadhatja a ctornak, de nem muszáj – lehetősége van arra, hogy átadja azt/azokat a tisztán virtuális doit() metódusnak. Feltételezem, hogy a x-et a doit()-ba, a y-t és/vagy a z-t pedig a ctorokba akarod átadni:

class Funct {public: virtual int doit(int x) = 0;};

Ezután három függvény helyett három származtatott osztályt hozol létre:

Most láthatod, hogy a ctor paraméterei befagynak a függvényoidba, amikor létrehozod a array ofunctionoidokat:

Így amikor a felhasználó meghívja a doit()-t az egyik ilyen functionoidon, megadja a “maradék” argokat, és a híváskoncepcionálisan kombinálja a ctornak átadott eredeti argokat a doit() metódusba átadott argokkal:

array->doit(12);

Amint már utaltam rá, a functionoidok egyik előnye, hogy több példánya is lehet mondjuk a Funct1-nek a tömbünkben, és ezekbe a példányokba különböző paramétereket fagyaszthatunk be. Például array ésarray mindkettő Funct1 típusú, de a array->doit(12) viselkedése más lesz, mint aarray->doit(12) viselkedése, mivel a viselkedés függ mind a doit()-nak átadott 12-től, mind a ctorsnak átadott argtól.

A functionoidok másik előnye nyilvánvaló, ha a példát egy array of functionoidról egy localfunctionoidra változtatjuk. A színpadra állításhoz térjünk vissza a régimódi függvénymutatós megközelítéshez, és képzeljük el, hogy egy összehasonlító függvényt próbálunk átadni egy sort() vagy binarySearch() rutinnak. A sort() vagy binarySearch()rutin neve childRoutine(), az összehasonlító függvénymutató típusa pedig FunctPtr:

void childRoutine(FunctPtr f){ // ... f( /*...args...*/ ); // ...}

A különböző hívók különböző függvénymutatókat adnának át attól függően, hogy mit tartanak a legjobbnak:

void myCaller(){ // ... childRoutine(funct1); // ...}void yourCaller(){ // ... childRoutine(funct3); // ...}

Ezt a példát könnyen lefordíthatjuk egy olyan példára, amely a functionoidokat használja:

Ezzel a példával a háttérben két előnyét láthatjuk a functionoidoknak a function-pointerekkel szemben. A fent leírt “ctor args”-előny, valamint az a tény, hogy a functionoidok szálbiztos módon képesek fenntartani az állapotot a hívások között.A sima függvénymutatók esetében az emberek általában statikus adatokon keresztül tartják fenn az állapotot a hívások között. A statikus adatok azonban nem önmagukban szálbiztosak – a statikus adatokat minden szál megosztja egymással. A functionoid megközelítés olyasmit biztosít, ami eredendően szálbiztos, mivel a kód a végén szál-lokális adatokkal rendelkezik. A megvalósítás egyszerű: változtassuk meg a régimódi statikus adatot a functionoid this objektumán belüli példányadattaggá, és tessék, az adatok nem csak szál-lokálisak, de még rekurzív hívások esetén is biztonságosak: minden egyes hívás a yourCaller()-hoz saját külön Funct3 objektummal rendelkezik, saját külön példányadattal.

Megjegyezzük, hogy nyertünk valamit anélkül, hogy bármit is veszítettünk volna. Ha szálglobális adatokat akarunk, a functionoidok is adhatnak youthat: csak változtassuk meg a functionoid this objektumán belüli példányadattagról egy statikus adattagra a functionoid osztályán belül, vagy akár egy helyi hatókörű statikus adatra. Nem lenne jobb, mint a funkciómutatókkal, de nem is lenne rosszabb.

A functionoid megközelítés ad egy harmadik lehetőséget, ami a régimódi megközelítéssel nem áll rendelkezésre: afunctionoid lehetővé teszi a hívók számára, hogy eldöntsék, hogy szál-lokális vagy szál-globális adatokat akarnak. Felelősséggel tartoznának a zárolások használatáért azokban az esetekben, amikor szálglobális adatokat akarnak, de legalább lenne választásuk. Ez egyszerű:

A funkcionoidok nem oldanak meg minden problémát, ami rugalmas szoftverek készítésekor felmerül, de szigorúan erősebbek, mint a függvénymutatók, és érdemes legalább kiértékelni őket. Valójában könnyen bebizonyíthatod, hogy a functionoidok nem veszítenek el semmilyen erőt a function-pointerekkel szemben, mivel elképzelhető, hogy a régimódi megközelítés a function-pointerekkel egyenértékű egy globális(!) functionoid objektummal. Mivel mindig létrehozhatsz egy globális functionoid objektumot, nem vesztettél semmit. QED.

Megoldható, hogy a functionoidok gyorsabbak legyenek, mint a normál függvényhívások?

Igen.

Ha van egy kis functionoidod, és a való világban ez elég gyakori, a függvényhívás költsége magas lehet a functionoid által végzett munka költségéhez képest. Az előző GYIK-ben a functionoidok virtuális függvényekkel voltakimplementálva, és jellemzően egy függvényhívás költsége. Egy alternatív megközelítés a sablonokat használja.

A következő példa szellemében hasonló az előző GYIK-ben szereplőhöz. A doit()-t átneveztemoperator()()-ra, hogy javítsam a hívókód olvashatóságát, és hogy valaki átadhasson egy szabályos függvénymutatót:

A különbség e megközelítés és az előző GYIK-ben szereplő között az, hogy a függvényoidot nem futásidőben, hanem fordítási időben “kötjük “a hívóhoz. Gondoljunk erre úgy, mint egy paraméter átadására: ha már fordítási időben tudjuk, hogy végül milyen típusú functionoidot akarunk átadni, akkor használhatjuk a fenti technikát, és legalábbis tipikus esetekben sebességelőnyre tehetünk szert abból, hogy a fordító a hívón belül a functionoid kódját inline-expanderrel bővíti. Íme egy példa:

template <typename FunctObj>void myCode(FunctObj f){ // ... f( /*...args-go-here...*/ ); // ...}

Amikor a fordító lefordítja a fentieket, lehet, hogy inline-expandálja a hívást, ami javíthatja a teljesítményt.

Itt van a fenti hívás egyik módja:

void blah(){ // ... Funct2 x("functionoids are powerful", 42); myCode(x); // ...}

Mellesleg: ahogy a fenti első bekezdésben utaltunk rá, normál függvények neveit is átadhatjuk (bár a hívó ezeket használva a függvényhívás költségeit viselheti):

void myNormalFunction(int x);void blah(){ // ... myCode(myNormalFunction); // ...}

Mi a különbség a functionoid és a functor között?

A functionoid egy olyan objektum, amelynek egy fő metódusa van. Alapvetően egy C-szerű függvény, például az printf() OO kiterjesztése. A functionoidot akkor használjuk, ha a függvénynek egynél több belépési pontja van (azaz egynél több “metódusa”) és/vagy a hívások közötti állapotot szálbiztos módon kell fenntartani (a C-stílusú megközelítés a hívások közötti állapot fenntartására az, hogy egy helyi “statikus” változót adunk a függvényhez, de ez borzasztóan nem biztonságos egy többszálas környezetben).

A functor a functionoid egy speciális esete: ez egy olyan functionoid, amelynek metódusa a “függvényhívó operátor”, operator()(). Mivel túlterheli a függvényhívás operátort, a kód ugyanolyan szintaxissal hívhatja meg a fő metódusát, mint egy függvényhívás esetében. Például, ha “foo” egy functor, akkor az “operator()()” metódus hívásához a “foo” objektumon azt kell mondanunk, hogy “foo()”. Ennek előnye a sablonoknál van, mivel ekkor a sablon rendelkezhet egy sablonparaméterrel, amelyet függvényként használnak, és ez a paraméter lehet akár egy függvény neve, akár egy functor-objektum. Teljesítményelőnye van annak, hogy ez egy functor-objektum, mivel az “operator()()” metódus inline-olható (míg ha egy függvény címét adod át, akkor annak szükségszerűen nem inline-olhatónak kell lennie).

Ez nagyon hasznos olyan dolgoknál, mint a “comparison” függvény a rendezett tárolókon. C-ben az összehasonlító függvényt mindig mutatóval adjuk át (pl, lásd a “qsort()” aláírását), de a C++-ban a paraméter érkezhet akár egy függvény mutatójaként, VAGY egy functor-objektum neveként, és az eredmény az, hogy a rendezett konténerek C++-ban bizonyos esetekben sokkal gyorsabbak (és soha nem lassabbak) lehetnek, mint a megfelelő C-ben.

Mivel a Java-ban nincs semmi hasonló a sablonokhoz, dinamikus kötést kell használnia minden ilyen dologhoz, és a dinamikus kötés szükségképpen függvényhívást jelent. Normális esetben nem nagy ügy, de a C++-ban rendkívül nagy teljesítményű kódot akarunk lehetővé tenni. Ez azt jelenti, hogy a C++ a “csak akkor fizetsz érte, ha használod” filozófiát követi, ami azt jelenti, hogy a nyelvnek soha nem szabad önkényesen többletköltséget felszámolnia, mint amire a fizikai gép képes (természetesen a programozó opcionálisan használhat olyan technikákat, mint a dinamikus kötés, amelyek általában némi többletköltséget jelentenek a rugalmasságért vagy más “képességért” cserébe, de a tervező és a programozó dönti el, hogy akarja-e az ilyen konstrukciók előnyeit (és költségeit).

Leave a Reply