Standaard C++

Pointers naar lidfuncties

Is het type van “pointer-to-member-function” anders dan “pointer-to-function”?

Ja.

Beschouw de volgende functie:

int f(char a, float b);

Het type van deze functie is verschillend, afhankelijk van het feit of het een gewone functie is of een nietstatic lidfunctie van een of andere klasse:

Note: als het een static lidfunctie is van class Fred, is het type hetzelfde als wanneer het een gewone functie zou zijn:”int (*)(char,float)“.

Hoe geef ik een pointer-naar-lid-functie door aan een signaalhandler, X event callback, systeemaanroep die een thread/task start, etc?

Niet doen.

Omdat een lid-functie geen betekenis heeft zonder een object om het op aan te roepen, kun je dit niet direct doen (als Het X WindowSysteem was herschreven in C++, zou het waarschijnlijk verwijzingen naar objecten doorgeven, niet alleen pointers naar functies;natuurlijk zouden de objecten de vereiste functie belichamen en waarschijnlijk nog veel meer).

Als een patch voor bestaande software, gebruik een top-level (niet-lid) functie als een wrapper die een object neemt verkregen door een andere techniek. Afhankelijk van de routine die je aanroept, kan deze “andere techniek” triviaal zijn of een beetje werk van jou vergen. De systeemaanroep die een thread start, bijvoorbeeld, kan vereisen dat je een functie pointer doorgeeft samen met een void*, zodat je de object pointer in de void* kunt doorgeven. Veel real-time besturingssystemen doen iets soortgelijks voor de functie die een nieuwe taak start. In het ergste geval zou je de object pointer in een globale variabele kunnen opslaan; dit kan nodig zijn voor Unix signaal afhandelingen (maar globals zijn, in het algemeen, ongewenst). In ieder geval zou de top-level functie de gewenste member-functie op het object aanroepen.

Hier volgt een voorbeeld van het slechtste geval (gebruik van een global). Stel dat je Fred::memberFn() wilt aanroepen op interrupt:

Note: static member-functies vereisen geen echt object om te worden aangeroepen, dus pointers-naarstatic-member-functies zijn meestal type-compatibel met gewone pointers-naar-functies. Maar, hoewel het waarschijnlijk werkt op de meeste compilers, zou het eigenlijk een extern "C" niet-lid functie moeten zijn om correct te zijn, omdat “C linkage” niet alleen dingen omvat zoals name mangling, maar ook aanroep conventies, die verschillend kunnen zijn tussen C en C++.

Waarom krijg ik steeds compile errors (type mismatch) als ik een member functie probeer te gebruiken als een interrupt service routine?

Dit is een speciaal geval van de vorige twee vragen, lees daarom eerst de vorige twee antwoorden.

Niet-static member functies hebben een verborgen parameter die correspondeert met de this pointer. De this pointer wijst naar de instance data voor het object. De interrupt hardware/firmware in het systeem is niet in staat om hetthis pointer argument te leveren. U moet “normale” functies (niet klasse leden) of static member functies gebruiken als interrupt service routines.

Een mogelijke oplossing is om een static member te gebruiken als de interrupt service routine en die functie ergens te laten zoeken om het instantie/member paar te vinden dat moet worden aangeroepen bij interrupt. Het effect is dus dat een member functie wordt aangeroepen op een interrupt, maar om technische redenen moet je eerst een tussenliggende functie aanroepen.

Waarom heb ik problemen met het nemen van het adres van een C++ functie?

Kort antwoord: als je het probeert op te slaan in (of door te geven als) een pointer-to-functie, dan is dat het probleem – dit is een uitvloeisel van de vorige FAQ.

Lang antwoord: In C++ hebben memberfuncties een impliciete parameter die naar het object wijst (de this pointerinside van de memberfunctie). Normale C functies hebben een andere aanroep-conventie dan member-functies, dus de types van hun pointers (pointer-to-member-function vs pointer-to-function) zijn verschillend en onverenigbaar. C++ introduceert een nieuw type pointer, een pointer-to-member, die alleen kan worden aangeroepen door een object te leveren.

-NOOT: probeer niet om een pointer-to-member-functie te “casten” in een pointer-to-functie; het resultaat is ongedefinieerd en waarschijnlijk desastreus. Een pointer-to-member-functie hoeft bijvoorbeeld niet het machine-adres van de juiste functie te bevatten. Zoals gezegd in het laatste voorbeeld, als je een pointer hebt naar een gewone C functie, gebruik dan of atop-level (niet-lid) functie, of een static (klasse) lid functie.

Hoe kan ik syntax fouten vermijden bij het maken van pointers naar leden?

Gebruik een typedef.

Ja, juist, ik weet het: jij bent anders. Jij bent slim. Je kunt dit doen zonder een typedef. Zucht. Ik heb veel emails gekregen van mensen die, net als jij, weigerden het simpele advies van deze FAQ op te volgen. Ze verspilden uren en uren van hun tijd, terwijl 10 seconden aan typedefs hun leven zouden hebben vereenvoudigd. En geef toe, u schrijft geen code die alleen u kunt lezen; u schrijft uw code hopelijk zodat anderen die ook kunnen lezen – als ze moe zijn – als ze hun eigen deadlines en hun eigen uitdagingen hebben. Dus waarom zou je het jezelf en anderen opzettelijk moeilijker maken? Wees slim: gebruik een typedef.

Hier is een voorbeeldklasse:

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

De typedef is triviaal:

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

Dat is het! FredMemFn is de typenaam, en een pointer van dat type wijst naar elk lid van Fred dat(char,float) neemt, zoals Fred’s f, g, h en i.

Het is dan triviaal om een lid-functie pointer te declareren:

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

En het is ook triviaal om functies te declareren die lid-functie pointers ontvangen:

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

En het is ook triviaal om functies te declareren die lid-functie pointers retourneren:

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

Dus alsjeblieft, gebruik een typedef. Of dat, of stuur me geen email over de problemen die je hebt met je lid-functie pointers!

Hoe kan ik syntax fouten vermijden bij het aanroepen van een lid-functie met behulp van een pointer-to-member-functie?

Als je toegang hebt tot een compiler en standaard bibliotheek die de juiste delen van de aankomende C++17 standaard implementeert, gebruik dan std::invoke. Gebruik anders een #define macro.

Gelieve.

Gelieve.

Ik krijg veel te veel emails van verwarde mensen die geweigerd hebben dit advies op te volgen. Het is zo simpel. Ik weet dat u geenstd::invoke of een macro nodig hebt, en de expert met wie u sprak kan het ook zonder een van beide, maar laat uw ego alstublieft niet in de weg staan van wat belangrijk is: geld. Andere programmeurs zullen je code moeten lezen / onderhouden. Ja, ik weet het: je bent slimmer dan alle anderen; prima. En je bent geweldig; prima. Maar voeg geen onnodige complexiteit toe aan je code.

Het gebruik van std::invoke is triviaal. Opmerking: FredMemFn is een typedef voor een pointer-to-membertype:

Als u std::invoke niet kunt gebruiken, verminder dan de onderhoudskosten door, paradoxaal genoeg, een #define macro te gebruiken in dit specifieke geval.

(Normaal heb ik een hekel aan #define macro’s, maar je moet ze gebruiken met pointers tomembers omdat ze de leesbaarheid en schrijfbaarheid van dat soort code verbeteren.)

De macro is triviaal:

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

Het gebruik van de macro is ook triviaal. Opmerking: FredMemFn is een typedef voor een pointer-to-membertype:

De reden waarom std::invoke of deze macro een goed idee is, is omdat memberfunctie-aanroepen vaak veel complexer zijn dan het eenvoudige voorbeeld dat zojuist is gegeven. Het verschil in leesbaarheid en schrijfbaarheid is aanzienlijk. comp.lang.c++ heeft honderden en honderden postings moeten doorstaan van verwarde programmeurs die de syntaxis niet goed konden krijgen. Bijna al deze fouten zouden verdwenen zijn als ze std::invoke of de bovenstaande macro hadden gebruikt.

Note: #define macro’s zijn kwaadaardig op 4 verschillende manieren: kwaadaardig#1,kwaadaardig#2, kwaadaardig#3, en kwaadaardig#4. Maar soms zijn ze nog steeds nuttig. Maar je moet nog steeds een vaag gevoel van schaamte hebben na het gebruik ervan.

Hoe maak en gebruik ik een array van pointer-to-member-functie?

Gebruik zowel de typedef als std::invoke of de eerder beschreven #define macro, en je bent voor 90% klaar.

Stap 1: maak een typedef:

Stap 2: maak een #define macro als u geen std::invoke hebt:

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

Nu is uw array van pointers-to-member-functions rechttoe rechtaan:

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

En je gebruik van een van de member-functie pointers is ook rechttoe rechtaan:

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

of als je std::invoke niet hebt,

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

Note: #define macro’s zijn kwaadaardig op 4 verschillende manieren: kwaad#1, kwaad#2, kwaad#3, en kwaad#4. Maar soms zijn ze nog steeds nuttig. Schaam je, voel je schuldig, maar als een kwaadaardige constructie als een macro je software verbetert, gebruik hem dan.

Hoe declareer ik een pointer-to-member-functie die wijst naar een const member-functie?

Kort antwoord: voeg een const toe rechts van de ) wanneer je een typedef gebruikt om het member-functie-pointertype te declareren.

Voor stel dat u bijvoorbeeld een pointer-to-member-functie wilt die wijst naar Fred::f, Fred::g of Fred::h:

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

Wanneer u dan een typedef gebruikt om het member-functie-pointertype te declareren, moet het er zo uitzien:

Dat is het!

Dan kunt u member-functie pointers declareren/pass/returnen net als normaal:

Wat is het verschil tussen de .* en ->* operatoren?

U hoeft dit niet te begrijpen als u std::invoke of een macro gebruikt voor member-functie-pointer aanroepen. Oyea, gebruik in dit geval std::invoke of een macro. En had ik al gezegd dat u in dit geval std::invoke of een macro moet gebruiken?!?

Bijv.:

Maar overweeg in plaats daarvan het gebruik van een std::invoke of een macro:

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

of

Zoals eerder besproken, zijn aanroepen in de echte wereld vaak veel gecompliceerder dan de simpleones hier, dus het gebruik van een std::invoke of een macro zal doorgaans de schrijfbaarheid en leesbaarheid van uw code verbeteren.

Kan ik een pointer naar een member-functie converteren naar een void*?

Nee!

Technische details: pointers naar member-functies en pointers naar data worden niet noodzakelijkerwijs op dezelfde manier gerepresenteerd. Een aanwijzer naar een lidfunctie kan een gegevensstructuur zijn in plaats van een enkele aanwijzer. Denk er eens over na: als het wijst naar een virtuele functie, wijst het misschien niet naar een statisch oplosbaar hoopje code, dus het is misschien niet eens een normaal adres – het kan een andere datastructuur van een soort zijn.

Gelieve me niet te e-mailen als het bovenstaande lijkt te werken op uw specifieke versie van uw specifieke compiler op uw specifieke besturingssysteem. Kan me niet schelen. Het is illegaal, punt.

Kan ik een pointer-naar-functie converteren naar een void*?

Nee!

Technische details: void*pointers zijn pointers naar gegevens, en functiepointers wijzen naar functies. De taal vereist niet dat functies en gegevens in dezelfde adresruimte, dus, bij wijze van voorbeeld en niet beperking, op architecturen die ze hebben in verschillende adresruimten, de twee verschillende pointer types zullen niet vergelijkbaar zijn.

Gelieve me niet e-mailen als het bovenstaande lijkt te werken op uw specifieke versie van uw specifieke compiler op uwparticular besturingssysteem. Kan me niet schelen. Het is illegaal, punt uit.

Ik heb iets nodig als functie-pointers, maar met meer flexibiliteit en/of thread-safety; is er een andere manier?

Gebruik een functionoid.

Wat is een functionoid, en waarom zou ik er een gebruiken?

Functionoids zijn functies op steroïden. Functionoids zijn veel krachtiger dan functies, en die extra kracht lost een aantal (niet alle) problemen op die zich voordoen bij het gebruik van functie-pointers.

Laten we een voorbeeld geven van een traditioneel gebruik van functie-pointers, en dan vertalen we dat voorbeeld naar functionoids. Het traditionele functie-punters idee is om een stel compatibele functies te hebben:

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

Die je dan benadert door functie-punters:

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

Soms maken mensen een array van deze functie-punters:

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

In welk geval ze de functie aanroepen door de array te benaderen:

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

Met functionoids maak je eerst een basisklasse met een pure-virtual methode:

Dan in plaats van drie functies, maak je drie afgeleide klassen:

Dan in plaats van een functie-pointer door te geven, geef je een Funct* door. Ik maak een typedef genaamd FunctPtr alleen maar om de rest van de code te laten lijken op de ouderwetse aanpak:

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

Je kunt een array van hen maken op bijna dezelfde manier:

Dit geeft ons de eerste hint over waar functionoids strikt genomen krachtiger zijn dan functie-pointers: het feit dat de functionoid benadering argumenten heeft die je kunt doorgeven aan de ctors (hierboven weergegeven als …ctor-args…) terwijl de functie-pointers versie dat niet doet. Denk aan een functionoid object als een gevriesdroogde functie-aanroep (nadruk op het woord aanroep). In tegenstelling tot een pointer naar een functie, is een functionoid (conceptueel) een pointer naar een gedeeltelijk aangeroepen functie. Stel je even een technologie voor waarmee je enkele-maar-niet-alle argumenten aan een functie kunt doorgeven, en waarmee je vervolgens die (gedeeltelijk voltooide) aanroep kunt bevriezen. Stel je voor dat die technologie je een soort magische pointer teruggeeft naar die gevriesdroogde gedeeltelijk voltooide functie-aanroep. Later geef je de resterende argumenten door met die pointer, en het systeem neemt op magische wijze je originele argumenten (die bevroren waren), combineert ze met alle lokale variabelen die de functie berekende voordat ze bevroren werd, combineert dat alles met de nieuw doorgegeven argumenten, en gaat verder met de uitvoering van de functie waar die was gestopt toen hij bevroren werd. Dat klinkt misschien als science fiction, maar het is in principe wat functionoids je laten doen. En je kunt die gevriesdroogde functie herhaaldelijk “voltooien” met verschillende “resterende parameters”, zo vaak als je wilt. Plus ze staan toe (niet vereisen) dat je de gevriesdroogde toestand verandert wanneer het wordt aangeroepen, wat betekent dat functionoids informatie kunnen onthouden van de ene aanroep naar de volgende.

Laten we weer met beide benen op de grond komen en we zullen een paar voorbeelden uitwerken om uit te leggen wat al die mumbo jumbo werkelijk betekent.

Stel dat de oorspronkelijke functies (in de ouderwetse functie-pointer stijl) iets andere parameters hadden.

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

Wanneer de parameters verschillend zijn, is de ouderwetse functie-pointer aanpak moeilijk te gebruiken, omdat de aanroeper niet weet welke parameters hij moet doorgeven (de aanroeper heeft slechts een pointer naar de functie, niet de naam van de functie of, wanneer de parameters verschillend zijn, het aantal en de typen van de parameters) (schrijf me hierover geen e-mail; ja, het kan, maar dan moet je op je hoofd gaan staan en rommelige dingen doen; maar schrijf me er niet over – gebruik in plaats daarvan functie-pointertjes).

Met functionoïden is de situatie, althans soms, veel beter. Aangezien een functionoid kan worden gezien als een droog-gedroogde functie-aanroep, neem je gewoon de niet-gemeenschappelijke args, zoals die ik y en/of z heb genoemd, en maak je zeargs voor de corresponderende ctors. Je mag ook de gemeenschappelijke args (in dit geval de int genaamd x) doorgeven aan de ctor, maar dat hoeft niet – je hebt de optie om ze in plaats daarvan door te geven aan de zuiver virtuele doit() methode. Ik neem aan dat je x wilt doorgeven aan doit() en y en/of z aan de ctors:

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

Dan in plaats van drie functies, maak je drie afgeleide klassen:

Nu zie je dat de parameters van de ctor worden gevriesdroogd in de functionoid wanneer je de array van functionoids maakt:

Dus wanneer de gebruiker de doit() op een van deze functionoids aanroept, levert hij de “resterende” args, en de call combineert conceptueel de oorspronkelijke args doorgegeven aan de ctor met die doorgegeven in de doit() methode:

array->doit(12);

Zoals ik al heb laten doorschemeren, is een van de voordelen van functionoids dat je verschillende instanties van, zeg, Funct1 in je array kunt hebben, en die instanties kunnen verschillende parameters hebben die er in zijn gevriesdroogd. Bijvoorbeeld, array enarray zijn beide van het type Funct1, maar het gedrag van array->doit(12) zal verschillend zijn van het gedrag vanarray->doit(12), omdat het gedrag zal afhangen van zowel de 12 die werd doorgegeven aan doit() als de args die werden doorgegeven aan de ctors.

Een ander voordeel van functionoids wordt duidelijk als we het voorbeeld veranderen van een array van functionoids naar een localfunctionoid. Laten we, om de situatie in kaart te brengen, teruggaan naar de ouderwetse functie-pointer benadering, en ons voorstellen dat u probeert een vergelijkingsfunctie door te geven aan een sort() of binarySearch() routine. De sort() of binarySearch()routine heet childRoutine() en het type vergelijkingsfunctie-pointer heet FunctPtr:

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

Dan zouden verschillende aanroepers verschillende functie-pointers doorgeven, afhankelijk van wat zij dachten dat het beste was:

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

We kunnen dit voorbeeld eenvoudig vertalen naar een voorbeeld met functie-pointers:

Gezien dit voorbeeld als achtergrond, kunnen we twee voordelen zien van functie-pointers boven functie-pointers. Het “ctor args”-voordeel dat hierboven is beschreven, plus het feit dat functionoids de toestand tussen aanroepen op een thread-veilige manier kunnen behouden. Met gewone functie-pointers behouden mensen de toestand tussen aanroepen normaal gesproken via statische gegevens. Statische gegevens zijn echter niet intrinsiek thread-safe – statische gegevens worden gedeeld tussen alle threads. De functie-pointers aanpak geeft je iets dat intrinsiek thread-safe is omdat de code eindigt met thread-local data. De implementatie is eenvoudig: verander de ouderwetse statische data in een instance-datalid binnen het this-object van de functionoid, en poef, de data is niet alleen thread-local, maar het is zelfs veilig met recursieve aanroepen: elke aanroep van yourCaller() zal zijn eigen afzonderlijke Funct3-object hebben met zijn eigen afzonderlijke instance-data.

Merk op dat we iets gewonnen hebben zonder iets te verliezen. Als je thread-globale data wilt, kunnen functionoids die ook geven: verander het gewoon van een instance data lid binnen het functionoid’s this object naar een static data lid binnen de klasse van de functionoid, of zelfs naar een local-scope static data. Je zou niet beter af zijn dan met functie-pointers, maar je zou ook niet slechter af zijn.

De functie-oid benadering geeft je een derde optie die niet beschikbaar is met de ouderwetse aanpak: de functie-oid laat aanroepers beslissen of ze thread-local of thread-global data willen. Ze zouden verantwoordelijk zijn voor het gebruik van hangsloten in gevallen waarin ze thread-globale gegevens willen, maar ze zouden in ieder geval de keuze hebben. Het is gemakkelijk:

Functionoids lossen niet elk probleem op dat zich voordoet bij het maken van flexibele software, maar ze zijn veel krachtiger dan functie-pointers en ze zijn het op zijn minst waard om te evalueren. In feite kun je gemakkelijk bewijzen dat functionoids geen kracht verliezen ten opzichte van functie-pointers, omdat je je kunt voorstellen dat de ouderwetse aanpak van functie-pointers gelijk staat aan het hebben van een globaal(!) functionoid object. Omdat je altijd een globaal functionoid object kunt maken, heb je geen terrein verloren. QED.

Kun je functionoids sneller maken dan normale functie-aanroepen?

Ja.

Als je een kleine functionoid hebt, en in de echte wereld is dat vrij gebruikelijk, kunnen de kosten van de functie-aanroep hoog zijn in vergelijking met de kosten van het werk dat door de functionoid wordt gedaan. In de vorige FAQ werden functieoïden geïmplementeerd met behulp van virtuele functies en dat kost meestal een functie-aanroep. Een alternatieve benadering maakt gebruik van templates.

Het volgende voorbeeld lijkt in de geest op het voorbeeld in de vorige FAQ. Ik heb doit() hernoemd naaroperator()() om de code voor de aanroeper leesbaarder te maken en om iemand in staat te stellen een gewone functie-pointer door te geven:

Het verschil tussen deze aanpak en die in de vorige FAQ is dat de fuctionoid wordt “gebonden” aan de aanroeper tijdens compile-tijd in plaats van tijdens runtime. Zie het als het doorgeven van een parameter: als je compileer-tijd weet wat voor soort functionoid je uiteindelijk wilt doorgeven, dan kun je de bovenstaande techniek gebruiken, en je kunt, althans in typische gevallen, een snelheidsvoordeel halen uit het feit dat de compiler de functionoid code binnen de aanroeper inline-expandeert. Hier is een voorbeeld:

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

Wanneer de compiler het bovenstaande compileert, zou hij de aanroep kunnen inline-expanderen, wat de prestaties zou kunnen verbeteren.

Hier is een manier om het bovenstaande aan te roepen:

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

Nabij: zoals in de eerste alinea hierboven werd gezinspeeld, kunt u ook de namen van normale functies doorgeven (hoewel u de kosten van de functie-aanroep zou kunnen oplopen wanneer de aanroeper deze gebruikt):

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

Wat is het verschil tussen een functionoid en een functor?

Een functionoid is een object dat één belangrijke methode heeft. Het is in feite de OO uitbreiding van een C-achtige functie, zoalsprintf(). Men zou een functionoid gebruiken wanneer de functie meer dan één ingangspunt heeft (d.w.z. meer dan één “methode”) en/of de toestand tussen aanroepen op een thread-veilige manier moet behouden (de C-stijl benadering om de toestand tussen aanroepen te behouden is om een lokale “statische” variabele aan de functie toe te voegen, maar dat is verschrikkelijk onveilig in een multi-threadeden omgeving).

Een functor is een speciaal geval van een functionoid: het is een functionoid waarvan de methode de “functie-aanroep operator, “operator()() is. Aangezien het de functie-aanroep operator overload, kan code zijn belangrijkste methode aanroepen met dezelfde syntaxis die ze zouden doen voor een functie-aanroep. Bijvoorbeeld, als “foo” een functor is, om de “operator()()” methode op het “foo” object aan te roepen zou men “foo()” zeggen. Het voordeel hiervan is in sjablonen, omdat het sjabloon dan een sjabloon-parameter kan hebben die zal worden gebruikt als een functie, en deze parameter kan ofwel de naam van een functie of een functor-object zijn. Er is een prestatievoordeel als het een functor-object is, omdat de “operator()()” methode kan worden geinlined (terwijl als je het adres van een functie doorgeeft, het noodzakelijkerwijs niet geinlined moet zijn).

Dit is erg handig voor dingen als de “vergelijking” functie op gesorteerde containers. In C, wordt de vergelijkingsfunctie altijd doorgegeven door een pointer (b.v, zie de signature van “qsort()”), maar in C++ kan de parameter binnenkomen als een pointer naar een functie OF als de naam van een functor-object, en het resultaat is dat gesorteerde containers in C++ in sommige gevallen een stuk sneller (en nooit langzamer) kunnen zijn dan het equivalent in C.

Omdat Java niets vergelijkbaars heeft met templates, moet het dynamische binding gebruiken voor al deze dingen, en dynamische binding betekent noodzakelijkerwijs een functie-aanroep. Normaal gesproken is dat niet zo erg, maar in C++ willen we extreem snelle code mogelijk maken. Dat wil zeggen, C++ heeft een “betaal er alleen voor als je het gebruikt” filosofie, wat betekent dat de taal nooit willekeurig meer kosten mag maken dan wat de fysieke machine kan (natuurlijk mag een programmeur, optioneel, technieken gebruiken zoals dynamische binding die, in het algemeen, wat kosten met zich meebrengen in ruil voor flexibiliteit of een andere “ility”, maar het is aan de ontwerper en programmeur om te beslissen of ze de voordelen (en kosten) van zulke constructies willen).

Leave a Reply