Standard C++

Punteri la funcții membre

Este tipul de „pointer-to-member-function” diferit de „pointer-to-function”?

Da.

Considerați următoarea funcție:

int f(char a, float b);

Tipul acestei funcții este diferit în funcție de faptul că este o funcție obișnuită sau o funcție non-static membră a unei clase:

Nota: dacă este o funcție membră static a class Fred, tipul ei este același ca și cum ar fi o funcție obișnuită:”int (*)(char,float)„.

Cum pot trece un pointer-la-funcție-membru către un gestionar de semnal, un callback de eveniment X, un apel de sistem care pornește un fir de execuție/task, etc.?

Nu o faceți.

Pentru că o funcție membră este lipsită de sens fără un obiect pe care să fie invocată, nu puteți face acest lucru în mod direct (dacă The X WindowSystem ar fi rescris în C++, probabil că ar trece referințe la obiecte, nu doar pointeri la funcții;în mod natural, obiectele ar încorpora funcția necesară și probabil multe altele).

Ca un patch pentru software-ul existent, folosiți o funcție de nivel superior (nemembru) ca un wrapper care ia un obiect obținut printr-o altă tehnică. În funcție de rutina pe care o apelați, această „altă tehnică” ar putea fi trivială sau ar puteanecesita puțină muncă din partea dumneavoastră. Apelul de sistem care pornește un fir, de exemplu, ar putea să vă ceară să treceți un pointer de funcție împreună cu un void*, astfel încât să puteți trece pointerul obiectului în void*. Multe sisteme de operare în timp real fac ceva similar pentru funcția care inițiază o nouă sarcină. În cel mai rău caz, ați putea stoca pointerul obiectului într-o variabilă globală; acest lucru ar putea fi necesar pentru gestionarii de semnale Unix (dar, în general, variabilele globale nu sunt dorite). În orice caz,funcția de nivel superior ar apela funcția membră dorită a obiectului.

Iată un exemplu din cel mai rău caz (folosind un global). Să presupunem că doriți să apelați Fred::memberFn() pe întrerupere:

Nota: funcțiile membre static nu necesită un obiect real pentru a fi invocate, astfel încât indicatoarele la funcțiile membrestatic sunt de obicei compatibile din punct de vedere al tipului cu indicatoarele la funcții obișnuite. Cu toate acestea, deși probabil că funcționează pe majoritatea compilatoarelor, ar trebui să fie o funcție extern "C" non-member pentru a fi corectă, deoarece „C linkage” nu acoperă doar lucruri precum manipularea numelor, ci și convențiile de apelare, care pot fi diferite între C și C++.

De ce continui să primesc erori de compilare (nepotrivire de tip) atunci când încerc să folosesc o funcție membră ca o rutină de serviciu de întrerupere?

Aceasta este un caz special al celor două întrebări anterioare, prin urmare citiți mai întâi cele două răspunsuri anterioare.

Funcțiile ne-membrestatic au un parametru ascuns care corespunde pointerului this. Pointerul this punctează datele de instanță pentru obiect. Hardware-ul/firmware-ul de întrerupere din sistem nu este capabil să furnizeze argumentul pointeruluithis. Trebuie să folosiți funcții „normale” (care nu sunt membre ale clasei) sau funcții membre static ca rutine de serviciu de întrerupere.

O soluție posibilă este de a folosi un membru static ca rutină de serviciu de întrerupere și de a face ca această funcție să caute undeva pentru a găsi perechea instanță/membru care ar trebui să fie apelată la întrerupere. Astfel, efectul este că o funcție membră este invocată la o întrerupere, dar, din motive tehnice, trebuie să apelați mai întâi o funcție intermediară.

De ce am probleme în a lua adresa unei funcții C++?

Răspuns scurt: dacă încercați să o stocați în (sau să o treceți ca) un pointer-la-funcție, atunci aceasta este problema – acesta este un corolar al întrebărilor frecvente anterioare.

Răspuns lung: În C++, funcțiile membre au un parametru implicit care indică obiectul (pointerul this în interiorul funcției membre). Funcțiile normale din C pot fi considerate ca având o convenție de apelare diferită de cea a funcțiilor-membru, astfel încât tipurile de pointeri ale acestora (pointer-to-member-function vs pointer-to-function) sunt diferite șiincompatibile. C++ introduce un nou tip de pointer, numit pointer-to-member, care poate fi invocat numai prin furnizarea unui obiect.

NOTA: nu încercați să „turnați” un pointer-to-member-function într-un pointer-to-function; rezultatul este nedefinit și probabil dezastruos. De exemplu, nu este necesar ca un pointer-la-funcție-membru să conțină adresa de mașină a funcției corespunzătoare. Așa cum s-a spus în ultimul exemplu, dacă aveți un pointer la o funcție C obișnuită, folosiți fie o funcție de nivel superior (nemembră), fie o funcție membră static (de clasă).

Cum pot evita erorile de sintaxă atunci când creez pointeri la membri?

Utilizați un typedef.

Da, corect, știu: sunteți diferit. Sunteți deștept. Puteți face aceste lucruri fără un typedef. Sigh. Am primit multe e-mailuri de la oameni care, la fel ca tine, au refuzat să urmeze sfatul simplu al acestei întrebări frecvente. Au irosit ore și ore din timpul lor, când 10 secunde de typedef le-ar fi simplificat viața. În plus, recunoașteți, nu scrieți un cod pe care numai dumneavoastră îl puteți citi; sperăm că vă scrieți codul pe care și alții vor putea să-l citească – când sunt obosiți – când au propriile termene limită și propriile provocări. Așadar, de ce să vă faceți intenționat viața mai grea pentru voi și pentru ceilalți? Fiți deștept: folosiți un typedef.

Iată un exemplu de clasă:

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); // ...};

Tipedef-ul este trivial:

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

Așa este! FredMemFn este numele tipului, iar un pointer de acest tip indică orice membru al Fred care ia(char,float), cum ar fi f, g, h și i din Fred.

Este apoi trivial să se declare un pointer membru-funcție:

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

Și este, de asemenea, trivial să se declare funcții care primesc pointeri membru-funcție:

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

Și este, de asemenea, trivial să se declare funcții care returnează pointeri membru-funcție:

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

Așa că, vă rog, folosiți un typedef. Fie asta, fie nu-mi trimiteți e-mailuri despre problemele pe care le aveți cu indicatorii de funcții-membru!

Cum pot evita erorile de sintaxă atunci când apelez o funcție membru folosind un pointer-to-member-function?

Dacă aveți acces la un compilator și la o bibliotecă standard care implementează părțile corespunzătoare ale viitorului standard C++17, folosiți std::invoke. În caz contrar, folosiți o macro #define.

Vă rog.

Vă rog foarte mult.

Rețin mult prea multe e-mailuri de la persoane confuze care au refuzat să urmeze acest sfat. Este atât de simplu. Știu, nu aveți nevoie destd::invoke sau de un macro, iar expertul cu care ați vorbit poate face acest lucru și fără niciunul dintre ele, dar vă rog să nu vă lăsați ego-ul să stea în calea a ceea ce este important: banii. Alți programatori vor trebui să citească / întrețină codul tău. Da, știu: sunteți mai deștept decât toți ceilalți; bine. Și ești grozav; bine. Dar nu adăugați complexitate inutilă codului dumneavoastră.

Utilizarea std::invoke este trivială. Notă: FredMemFn este un typedef pentru un pointer-to-membertype:

Dacă nu puteți utiliza std::invoke, reduceți costul de întreținere prin, paradoxal, utilizarea unui macro #define în acest caz particular.

(În mod normal, nu-mi plac macrourile #define, dar ar trebui să le folosiți cu pointeri la membri, deoarece îmbunătățesc lizibilitatea și scriere a acestui tip de cod.)

Macroul este trivial:

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

Utilizarea macroului este, de asemenea, trivială. Notă: FredMemFn este un typedef pentru un pointer-to-membertype:

Motivul pentru care std::invoke sau această macro este o idee bună este că invocările funcțiilor membre sunt adesea mult mai complexe decât exemplul simplu tocmai dat. Diferența în ceea ce privește lizibilitatea și scriere este semnificativă.comp.lang.c++ a trebuit să suporte sute și sute de postări de la programatori confuzi care nu reușeau să înțeleagă corect sintaxa. Aproape toate aceste erori ar fi dispărut dacă ar fi folosit std::invoke sau macroul de mai sus.

Nota: macrourile #define sunt rele în 4 moduri diferite: rău#1, rău#2, rău#3 și rău#4. Dar ele sunt totuși utile uneori. Dar ar trebui totuși să simțiți un vag sentiment de rușine după ce le folosiți.

Cum creez și folosesc un array de pointer-to-member-function?

Utilizați atât typedef cât și std::invoke sau macroul #define descris mai devreme și ați terminat în proporție de 90%.

Pasul 1: creați un typedef:

Pasul 2: creați un macro #define dacă nu aveți std::invoke:

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

Acum matricea dvs. de pointeri-la-funcții-membre este simplă:

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

Și utilizarea unuia dintre pointerii la funcțiile-membru este, de asemenea, simplă:

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

sau, dacă nu aveți std::invoke,

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

Nota: macro-urile #define sunt rele în 4 moduri diferite: rău#1, rău#2, rău#3 și rău#4. Dar ele sunt totuși utile uneori. Simțiți-vă rușinați, simțiți-vă vinovați, dar atunci când o construcție malefică, cum ar fi o macro, vă îmbunătățește software-ul, folosiți-o.

Cum declar un pointer-la-funcție-membru care indică o funcție-membru const?

Răspuns scurt: adăugați un const în dreapta lui ) atunci când folosiți un typedef pentru a declara tipul de pointer-funcție-membru.

De exemplu, să presupunem că doriți un pointer-la-funcție-membru care să puncteze la Fred::f, Fred::g sau Fred::h:

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

Atunci când folosiți un typedef pentru a declara tipul de membru-funcție-pointer, ar trebui să arate astfel:

Așa este!

Apoi puteți declara/pasa/returna pointeri de funcție-membru ca de obicei:

Care este diferența dintre operatorii .* și ->*?

Nu va fi nevoie să înțelegeți acest lucru dacă folosiți std::invoke sau o macro pentru apelurile de pointeri de funcție-membru. Ohyea, vă rugăm să folosiți std::invoke sau o macro în acest caz. Și am menționat că ar trebui să folosiți std::invoke sau un macro în acest caz?!?!?

De exemplu:

Dar vă rugăm să luați în considerare folosirea unui std::invoke sau a unui macro în schimb:

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);}

sau

După cum am discutat mai devreme, invocările din lumea reală sunt adesea mult mai complicate decât cele simple de aici, așa că folosirea unui std::invoke sau a unui macro va îmbunătăți în mod obișnuit capacitatea de scriere și de citire a codului dumneavoastră.

Pot converti un pointer la o funcție membră într-un void*?

Nu!

Detalii tehnice: Pointerii la funcțiile membre și pointerii la date nu sunt neapărat reprezentați în același mod. Un pointer la o funcție membră ar putea fi o structură de date, mai degrabă decât un singur pointer. Gândiți-vă la asta: dacă indică o funcție virtuală, s-ar putea să nu indice de fapt o grămadă de cod rezolvabilă static, deci s-ar putea să nu fie nici măcar o adresă normală – ar putea fi o structură de date diferită de un anumit tip.

Vă rog să nu-mi trimiteți un e-mail dacă cele de mai sus par să funcționeze pe versiunea particulară a compilatorului dvs. particular pe sistemul de operare particular. Nu-mi pasă. Este ilegal, punct.

Pot converti un pointer-la-funcție într-un void*?

Nu!

Detalii tehnice: void*pointerii sunt pointeri la date, iar pointerii de funcție punctează la funcții. Limbajul nu cere ca funcțiile și datele să se afle în același spațiu de adrese, așa că, cu titlu de exemplu și nu de limitare, pe arhitecturi care le au în spații de adrese diferite, cele două tipuri de pointeri diferiți nu vor fi comparabile.

Vă rog să nu-mi trimiteți un e-mail dacă cele de mai sus par să funcționeze pe versiunea particulară a compilatorului dvs. particular pe sistemul de operare dvs. particular. Nu-mi pasă. Este ilegal, punct.

Am nevoie de ceva asemănător cu pointerii de funcție, dar cu mai multă flexibilitate și/sau siguranță a firelor de execuție; există o altă cale?

Utilizați un functionoid.

Ce naiba este un functionoid și de ce aș folosi unul?

Functionoids sunt funcții pe steroizi. Functionoizii sunt strict mai puternici decât funcțiile, iar acest plus de putere rezolvă unele (nu toate) dintre provocările cu care te confrunți în mod obișnuit atunci când folosești pointeri-funcție.

Să lucrăm un exemplu care arată o utilizare tradițională a pointorilor-funcție, apoi vom transpune acel exemplu înfunctionoizi. Ideea tradițională a pointerilor de funcție este de a avea o grămadă de funcții compatibile:

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

Apoi le accesați prin intermediul pointerilor de funcție:

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

Câteodată oamenii creează un array al acestor pointeri de funcție:

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

În acest caz, ei apelează funcția prin accesarea array-ului:

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

Cu ajutorul functionoidelor, se creează mai întâi o clasă de bază cu o metodă pur virtuală:

Apoi, în loc de trei funcții, se creează trei clase derivate:

Apoi, în loc de a trece un pointer de funcție, se trece un Funct*. Voi crea un typedef numit FunctPtr doar pentru ca restul codului să fie asemănător cu abordarea de modă veche:

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

Puteți crea o matrice a acestora aproape în același mod:

Aceasta ne oferă primul indiciu despre locul în care functionoidii sunt strict mai puternici decât pointorii de funcții: faptul că abordarea functionoid are argumente pe care le puteți transmite ctorilor (prezentate mai sus ca …ctor-args…) în timp ce versiunea cu pointorii de funcții nu are. Gândiți-vă la un obiect functionoid ca la un apel de funcție liofilizat (accent pe cuvântul apel). Spre deosebire de un pointer la o funcție, un functionoid este (din punct de vedere conceptual) un pointer la o funcție numită parțial. Imaginați-vă, pentru moment, o tehnologie care vă permite să treceți unele, dar nu toate argumentele la o funcție, apoi vă permite să liofilizați acest apel (parțial finalizat). Imaginați-vă că această tehnologie vă oferă înapoi un fel de pointer magic către acel apel de funcție parțial finalizat prin înghețare. Apoi, mai târziu, treceți restul argumentelor folosind acel pointer, iar sistemul ia în mod magic argumentele originale (care au fost înghețate), le combină cu orice variabile locale pe care funcția le-a calculat înainte de a fi înghețată, combină toate acestea cu noile argumente transmise și continuă execuția funcției acolo unde a rămas când a fost înghețată. S-ar putea să sune a science-fiction, dar, din punct de vedere conceptual, aceasta este ceea ce vă permite să faceți cu ajutorul functionoidelor. În plus, ele vă permit să „completați” în mod repetat acea funcție-șoc liofilizată cu diferiți „parametri rămași”, ori de câte ori doriți. În plus, ele vă permit (nu vă obligă) să schimbați starea înghețată atunci când este apelată, ceea ce înseamnă că functionoizii pot reține informații de la un apel la altul.

Să ne punem din nou picioarele pe pământ și vom lucra cu câteva exemple pentru a explica ce înseamnă cu adevărat toată această mumbo jumbo.

Să presupunem că funcțiile originale (în stilul de modă veche al pointerilor de funcție) au luat parametri ușor diferiți.

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...*/ }

Când parametrii sunt diferiți, abordarea de modă veche a pointerilor de funcție este dificil de utilizat, deoarece apelantul nu știe ce parametri să treacă (apelantul are doar un pointer la funcție, nu și numele funcției sau, când parametrii sunt diferiți, numărul și tipurile parametrilor săi) (nu-mi scrieți un e-mail despre asta; da, o puteți face, dar trebuie să stați în cap și să faceți lucruri murdare; dar nu-mi scrieți despre asta – folosiți în schimb functionoids).

Cu functionoids, situația este, cel puțin uneori, mult mai bună. Din moment ce un functionoid poate fi gândit ca un apel la o funcție uscată prin congelare, nu trebuie decât să luați argajele necomune, cum ar fi cele pe care le-am numit y și/sau z, și să le transformați în argaje pentru ctorii corespunzători. Puteți, de asemenea, să treceți arhivele comune (în acest caz, int numit x) la ctor, dar nu sunteți obligat să o faceți – aveți opțiunea de a le trece în schimb la metoda virtuală pură doit(). Voi presupune că vreți să treceți x în doit() și y și/sau z în ctor:

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

Atunci, în loc de trei funcții, creați trei clase derivate:

Acum vedeți că parametrii ctorului sunt liofilizați în functionoid atunci când creați matricea de functionoid:

Așa că, atunci când utilizatorul invocă doit() pe unul dintre aceste functionoiduri, el furnizează parametrii „rămași”, iar apelul combină conceptual parametrii originali trecuți către ctor cu cei trecuți în metoda doit():

array->doit(12);

După cum am sugerat deja, unul dintre avantajele functionoidelor este că puteți avea mai multe instanțe de, să zicem, Funct1 în matricea dumneavoastră, iar aceste instanțe pot avea diferiți parametri liofilizați în ele. De exemplu, array șiarray sunt amândouă de tipul Funct1, dar comportamentul lui array->doit(12) va fi diferit de cel al luiarray->doit(12), deoarece comportamentul va depinde atât de 12 care a fost trecut la doit(), cât și de arginii trecuți către ctors.

Un alt beneficiu al functionoidelor este evident dacă schimbăm exemplul dintr-un array de functionoidelor într-un functionoid local. Pentru a pregăti terenul, să ne întoarcem la abordarea de modă veche a pointerilor de funcție și să ne imaginăm că încercați să treceți o funcție de comparație către o rutină sort() sau binarySearch(). Rutina sort() sau binarySearch() se numește childRoutine(), iar tipul de pointer-funcție de comparație se numește FunctPtr:

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

Atunci, diferiți apelanți ar trece diferiți pointeri-funcție în funcție de ceea ce consideră că este mai bine:

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

Putem traduce cu ușurință acest exemplu într-unul care utilizează functionoizi:

După acest exemplu ca fundal, putem vedea două avantaje ale functionoizilor față de function-pointeri. Beneficiul „ctor args „descris mai sus, plus faptul că functionoidii pot menține starea între apeluri într-o manieră thread-safe.Cu function-pointeri simpli, oamenii mențin în mod normal starea între apeluri prin date statice. Cu toate acestea, datele statice nu sunt în mod intrinsec sigure pentru fire – datele statice sunt partajate între toate firele. Abordarea de tip „functionoid” vă oferă ceva care este în mod intrinsec sigur pentru fire de execuție, deoarece codul se termină cu date locale pentru fire de execuție. Implementarea este trivială: schimbați datele statice de modă veche cu un membru de date de instanță în interiorul obiectului this al functionoidului și poftă bună, datele nu numai că sunt locale pentru firul de execuție, dar sunt sigure chiar și în cazul apelurilor recursive: fiecare apel la yourCaller() va avea propriul obiect Funct3 distinct cu propriile date de instanță distincte.

Rețineți că am câștigat ceva fără să pierdem nimic. Dacă doriți date globale pentru firul de execuție, functionoizii pot oferi și ei tinerețe: este suficient să le schimbați dintr-un membru de date de instanță din interiorul obiectului this al functionoidului într-un membru de date statice din interiorul clasei functionoidului, sau chiar într-un membru de date statice de domeniu local. Nu v-ar fi mai bine decât în cazul pointerelor funcționale, dar nici mai rău.

Abordarea functionoid vă oferă o a treia opțiune care nu este disponibilă în cazul abordării de modă veche: functionoidul permite apelanților să decidă dacă doresc date locale sau globale la nivelul firului de execuție. Aceștia ar fi responsabili să foloseascăelocks în cazurile în care doresc date globale ale firului, dar cel puțin ar avea posibilitatea de a alege. Este ușor:

Functionoizii nu rezolvă toate problemele întâlnite la realizarea de software flexibil, dar sunt strict mai puternici decât pointerii de funcție și merită cel puțin să fie evaluați. De fapt, puteți dovedi cu ușurință că functionoizii nu pierd nici o putere față de pointeri-funcție, deoarece vă puteți imagina că abordarea de modă veche a pointorilor-funcție esteechivalentă cu a avea un obiect functionoid global(!). Având în vedere că puteți crea oricând un obiect functionoid global, nu ați pierdut nimic. QED.

Puteți face functionoiduri mai rapide decât apelurile normale de funcție?

Da.

Dacă aveți un functionoid mic, iar în lumea reală acest lucru este destul de comun, costul apelului de funcție poate fi ridicatîn comparație cu costul muncii efectuate de functionoid. În întrebările frecvente anterioare, functionoidurile au fost implementate folosind funcții virtuale și, de obicei, vă costă un apel de funcție. O abordare alternativă utilizează șabloane.

Exemplul următor este similar în spirit cu cel din FAQ anterioară. Am redenumit doit() înoperator()() pentru a îmbunătăți lizibilitatea codului apelantului și pentru a permite cuiva să treacă un pointer de funcție obișnuit:

Diferența dintre această abordare și cea din FAQ anterioară este că fuctionoidul este „legat” de apelant la compilare și nu la execuție. Gândiți-vă că este ca și cum ați trece un parametru: dacă știți în momentul compilării tipul de functionoid pe care doriți să-l treceți în cele din urmă, atunci puteți folosi tehnica de mai sus și puteți, cel puțin în cazurile tipice, să obțineți un beneficiu de viteză dacă compilatorul extinde codul functionoidului în linie în cadrul apelantului. Iată un exemplu:

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

Când compilatorul compilează cele de mai sus, ar putea să extindă în linie apelul, ceea ce ar putea îmbunătăți performanța.

Iată un mod de a apela cele de mai sus:

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

În afară de aceasta: așa cum a fost sugerat în primul paragraf de mai sus, puteți, de asemenea, să treceți în numele funcțiilor normale (deși s-ar putea să suportați costul apelului de funcție atunci când apelantul le folosește):

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

Care este diferența dintre un functionoid și un functor?

Un functionoid este un obiect care are o metodă majoră. Este practic extensia OO a unei funcții de tip C, cum ar fiprintf(). Se folosește un functionoid ori de câte ori funcția are mai mult de un punct de intrare (adică mai multe „metode”) și/sau trebuie să mențină starea între apeluri într-o manieră sigură (abordarea de tip C pentru a menține starea între apeluri este de a adăuga o variabilă locală „statică” la funcție, dar acest lucru este îngrozitor de nesigur într-un mediu cu mai multe fire).

Un functor este un caz special al unui functionoid: este un functionoid a cărui metodă este „operatorul de apelare a funcției”, operator()(). Deoarece supraîncarcă operatorul de apelare a funcției, codul poate apela metoda sa majoră folosind aceeași sintaxă pe care ar folosi-o pentru un apel de funcție. De exemplu, dacă „foo” este un functor, pentru a apela metoda „operator()()” pe obiectul „foo” se va spune „foo()”. Avantajul acestui procedeu este în cazul șabloanelor, deoarece șablonul poate avea un parametru de șablon care va fi folosit ca o funcție, iar acest parametru poate fi fie numele unei funcții, fie un obiect functor. Faptul că este un obiect functor prezintă un avantaj de performanță, deoarece metoda „operator()()” poate fi aliniata (în timp ce dacă treceți adresa unei funcții, aceasta trebuie, în mod necesar, să nu fie aliniata).

Acest lucru este foarte util pentru lucruri precum funcția „comparison” din containerele sortate. În C, funcția de comparare esteîntotdeauna transmisă prin pointer (de ex, a se vedea semnătura pentru „qsort()”), dar în C++ parametrul poate veni fie ca un pointer la o funcție, fie ca numele unui obiect functor, iar rezultatul este că containerele sortate în C++ pot fi, în unele cazuri, mult mai rapide (și niciodată mai lente) decât echivalentul în C.

Din moment ce Java nu are nimic similar cu șabloanele, trebuie să folosească legătura dinamică pentru toate aceste lucruri, iar legătura dinamică înseamnă, în mod necesar, un apel de funcție. În mod normal, nu este o mare problemă, dar în C++ dorim să permitem un cod extrem de performant. Adică, C++ are o filozofie „plătești pentru el doar dacă îl folosești”, ceea ce înseamnă că limbajul nu trebuie niciodată să impună în mod arbitrar niciun fel de costuri suplimentare față de ceea ce mașina fizică este capabilă să realizeze (desigur, un programator poate, opțional, să folosească tehnici cum ar fi legarea dinamică care, în general, va impune unele costuri suplimentare în schimbul flexibilității sau al unei alte „ilități”, dar este la latitudinea proiectantului și programatorului să decidă dacă doresc beneficiile (și costurile) unor astfel de construcții).

.

Leave a Reply