Standard C++

Pointer till medlemsfunktioner

Är typen ”pointer-to-member-function” annorlunda än ”pointer-to-function”?

Ja.

Tänk på följande funktion:

int f(char a, float b);

Typen för denna funktion är olika beroende på om det är en vanlig funktion eller en ickestatic medlemsfunktion i någon klass:

Notera: om det är en static medlemsfunktion i class Fred är dess typ densamma som om det vore en vanlig funktion:”int (*)(char,float)”.

Hur överlämnar jag en pekare till en medlemsfunktion till en signalhanterare, en X-händelse callback, ett systemanrop som startar en tråd/uppgift osv?

Låt bli.

Eftersom en medlemsfunktion är meningslös utan ett objekt att åberopa den på, kan du inte göra detta direkt (om X WindowSystem skrevs om i C++ skulle det förmodligen skicka referenser till objekt, inte bara pekare till funktioner; naturligtvis skulle objekten förkroppsliga den önskade funktionen och förmodligen en hel del annat).

Som en patch för befintlig programvara, använd en funktion på högsta nivå (som inte är medlem) som en omslagsfunktion som tar emot ett objekt som erhållits genom någon annan teknik. Beroende på den rutin du anropar kan denna ”andra teknik” vara trivial eller kräva lite arbete från din sida. Systemanropet som startar en tråd kan till exempel kräva att du skickar en funktionspekare tillsammans med en void*, så att du kan skicka objektpekaren i void*. Många realtidsoperativsystem gör något liknande för den funktion som startar en ny uppgift. I värsta fall kan du lagra objektpekaren i en global variabel; detta kan krävas för Unix-signalhanterare (men globala variabler är i allmänhet oönskade). I vilket fall som helst skulle funktionen på högsta nivå anropa den önskade medlemsfunktionen på objektet.

Här är ett exempel på det sämsta fallet (med hjälp av en global). Anta att du vill anropa Fred::memberFn() vid avbrott:

Anmärkningar: static-medlemsfunktioner kräver inte ett faktiskt objekt för att anropas, sopointers-to-static-medlemsfunktioner är vanligtvis typkompatibla med vanliga pointers-to-funktioner. Även om det förmodligen fungerar i de flesta kompilatorer måste det dock vara en extern "C" icke-memberfunktion för att vara korrekt, eftersom ”C-länkning” inte bara omfattar saker som namnförvanskning, utan även anropskonventioner, som kan skilja sig åt mellan C och C++.

Varför får jag hela tiden kompileringsfel (type mismatch) när jag försöker använda en medlemsfunktion som en interrupt service routine?

Detta är ett specialfall av de två föregående frågorna, läs därför de två föregående svaren först.

Non-static medlemsfunktioner har en dold parameter som motsvarar thispekaren. this-pekaren pekar på instansdata för objektet. Systemets avbrottshårdvara/firmware kan inte tillhandahålla this pekarargumentet. Du måste använda ”normala” funktioner (som inte är klassmedlemmar) eller static medlemsfunktioner som avbrottsservicerutiner.

En möjlig lösning är att använda en static medlem som avbrottsservicerutin och låta den funktionen leta någonstans för att hitta instans/medlemsparet som ska anropas vid avbrottet. Effekten blir alltså att en medlemsfunktion anropas vid ett avbrott, men av tekniska skäl måste man först anropa en mellanliggande funktion.

Varför har jag problem med att ta adressen till en C++-funktion?

Kort svar: Om du försöker lagra den i (eller skicka den som) en pekare-till-funktion, så är det det som är problemet – detta är en följd av den tidigare FAQ:n.

Långt svar: I C++ har medlemsfunktioner en implicit parameter som pekar på objektet (thispekaren inom medlemsfunktionen). Normala C-funktioner kan betraktas som att de har en annan anropskonvention än medlemsfunktioner, så typerna av deras pekare (pointer-to-member-function vs. pointer-to-function) är olika och inkompatibla. C++ introducerar en ny typ av pekare, kallad pointer-to-member, som endast kan anropas genom att tillhandahålla ett objekt.

OBSERVERA: Försök inte att ”casta” en pointer-to-member-funktion till en pointer-to-funktion; resultatet är odefinierat och förmodligen katastrofalt. Det krävs t.ex. inte att en pekare-till-medlem-funktion innehåller maskinadressen till den lämpliga funktionen. Som det sades i det senaste exemplet, om du har en pekare till en vanlig C-funktion, använd antingen atop-level (non-member) funktion, eller en static (klass) medlemsfunktion.

Hur kan jag undvika syntaxfel när jag skapar pekare till medlemmar?

Använd en typedef.

Ja, okej, jag vet: du är annorlunda. Du är smart. Du kan göra det här utan typedef. Suck. Jag har fått många e-postmeddelanden från personer som, precis som du, vägrade att följa de enkla råden i denna FAQ. De slösade bort timmar ochtimmar av sin tid, när 10 sekunder av typedefs skulle ha förenklat deras liv. Och inse att du inte skriver kod som bara du kan läsa; du skriver förhoppningsvis kod som andra också kan läsa – när de är trötta – när de har egna deadlines och egna utmaningar. Så varför avsiktligt göra livet svårare för dig själv och andra? Var smart: använd en typedef.

Här är en exempelklass:

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

Typdefinitionen är trivial:

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

Det är allt! FredMemFn är typnamnet, och en pekare av den typen pekar på en medlem i Fred som tar(char,float), till exempel Freds f, g, h och i.

Det är då trivialt att deklarera en medlemsfunktionspekare:

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

Och det är också trivialt att deklarera funktioner som tar emot medlemsfunktionspekare:

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

Och det är också trivialt att deklarera funktioner som returnerar medlemsfunktionspekare:

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

Så använd gärna en typedef. Antingen det eller så skickar du inte e-post till mig om de problem du har med dina medlemsfunktionspekare!

Hur kan jag undvika syntaxfel när jag anropar en medlemsfunktion med hjälp av en pointer-to-member-function?

Om du har tillgång till en kompilator och ett standardbibliotek som implementerar lämpliga delar av den kommande C++17-standarden, använd std::invoke. Annars använder du ett #define-makro.

Vänligen.

Vänligen.

Jag får alldeles för många e-postmeddelanden från förvirrade personer som vägrade att följa detta råd. Det är så enkelt. Jag vet att du inte behöverstd::invoke eller ett makro, och experten du pratade med kan göra det utan någon av dem, men låt inte ditt ego stå i vägen för det som är viktigt: pengar. Andra programmerare kommer att behöva läsa/underhålla din kod. Ja, jag vet: du är smartare än alla andra; bra. Och du är fantastisk. Men lägg inte till onödig komplexitet i din kod.

Att använda std::invoke är trivialt. Observera: FredMemFn är ett typedef för en pointer-to-membertype:

Om du inte kan använda std::invoke, kan du minska underhållskostnaden genom att, paradoxalt nog, använda ett #define-makro i detta speciella fall.

(Normalt ogillar jag #define-makron, men du bör använda dem med pointers tomembers eftersom de förbättrar läsbarheten och skrivbarheten för den typen av kod.)

Makrot är trivialt:

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

Användning av makrot är också trivialt. Notera: FredMemFn är en typedef för en pointer-to-membertype:

Anledningen till att std::invoke eller det här makrot är en bra idé är att anrop av medlemsfunktioner ofta är mycket mer komplexa än det enkla exempel som just gavs. Skillnaden i läsbarhet och skrivbarhet är betydande.comp.lang.c++ har fått utstå hundratals och åter hundratals inlägg från förvirrade programmerare som inte riktigt fick syntaxen att stämma. Nästan alla dessa fel skulle ha försvunnit om de hade använt std::invoke eller ovanstående makro.

Note: #define-makron är onda på fyra olika sätt: evil#1,evil#2, evil#3 och evil#4. Men de är fortfarande användbara ibland. Men du bör ändå känna en vag känsla av skam efter att ha använt dem.

Hur skapar och använder jag en array av pointer-to-member-function?

Använd både typedef och std::invoke eller #define-makrot som beskrevs tidigare, så är du klar till 90 %.

Steg 1: skapa en typedef:

Steg 2: skapa ett #define-makro om du inte har std::invoke:

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

Nu är din array av pointers-to-member-funktioner okomplicerad:

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

Och din användning av en av medlemsfunktionspekarna är också enkel:

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

Och om du inte har std::invoke,

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

Notera: #define-makron är onda på fyra olika sätt: onda#1,onda#2, onda#3 och onda#4. Men de är fortfarande användbara ibland. Skäms, känner dig skyldig, men när en ond konstruktion som ett makro förbättrar din programvara, använd det.

Hur deklarerar jag en pointer-to-member-function som pekar på en const member-function?

Kort svar: lägg till en const till höger om ) när du använder en typedef för att deklarera member-function-pointertypen.

Antag till exempel att du vill ha en pekare-till-medlem-funktion som pekar på Fred::f, Fred::g eller Fred::h:

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

När du sedan använder en typedef för att deklarera medlem-funktions-pekartypen ska det se ut på följande sätt:

Så är det!

Då kan du deklarera/passa/återlämna medlemsfunktionspekare precis som vanligt:

Vad är skillnaden mellan operatörerna .* och ->*?

Du behöver inte förstå det här om du använder std::invoke eller ett makro för medlemsfunktionspekaranrop. Ohyea, använd std::invoke eller ett makro i det här fallet. Och nämnde jag att du bör använda std::invoke eller ett makro i det här fallet??!?

Till exempel:

Men överväg att använda en std::invoke eller ett makro i stället:

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

eller

Som vi diskuterat tidigare är anrop i verkligheten ofta mycket mer komplicerade än de enklaste här, så om du använder en std::invoke eller ett makro kommer du i regel att förbättra din kods skrivbarhet och läsbarhet.

Kan jag konvertera en pekare till en medlemsfunktion till en void*?

Nej!

Tekniska detaljer: pekare till medlemsfunktioner och pekare till data representeras inte nödvändigtvis på samma sätt. En pekare till en medlemsfunktion kan vara en datastruktur snarare än en enskild pekare. Tänk på det: om den pekar på en virtuell funktion kanske den faktiskt inte pekar på en statiskt upplösbar kodhög, så det kanske inte ens är en normal adress – det kanske är en annan datastruktur av något slag.

Skick inte ett e-postmeddelande till mig om ovanstående verkar fungera på just din version av just din kompilator på just ditt operativsystem. Jag bryr mig inte. Det är olagligt, punkt slut.

Kan jag konvertera en pekare-till-funktion till en void*?

Nej!

Tekniska detaljer: void* Pekare är pekare på data, och funktionspekare pekar på funktioner. Språket kräver inte att funktioner och data ska befinna sig i samma adressutrymme, så på arkitekturer som har dem i olika adressutrymmen kommer de två olika typerna av pekare inte att vara jämförbara.

Skick inte ett e-postmeddelande till mig om ovanstående verkar fungera i din specifika version av din specifika kompilator på ditt specifika operativsystem. Jag bryr mig inte. Det är olagligt, punkt slut.

Jag behöver något som liknar funktionsvisare, men med större flexibilitet och/eller trådsäkerhet; finns det något annat sätt?

Använd en functionoid.

Vad i helvete är en functionoid, och varför skulle jag använda en?

Functionoids är funktioner på steroider. Functionoids är strikt sett mer kraftfulla än funktioner, och den extra kraften löser några (inte alla) av de utmaningar som man vanligtvis ställs inför när man använder funktionsvisare.

Låt oss arbeta med ett exempel som visar en traditionell användning av funktionsvisare, sedan översätter vi det exemplet tillofunctionoids. Den traditionella idén med funktionsvisare är att man har ett gäng kompatibla funktioner:

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

Därefter får man tillgång till dessa med hjälp av funktionsvisare:

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

Ibland skapar man en array av dessa funktionsvisare:

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

I så fall anropar man funktionen genom att få tillgång till arrayen:

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

Med functionoids skapar man först en basklass med en rent virtuell metod:

Därefter skapar man i stället för tre funktioner tre härledda klasser:

Därefter skickar man i stället för att överlämna en funktions-pointer en Funct*. Jag skapar en typedef som heter FunctPtr bara för att göra resten av koden likadan som på det gamla sättet:

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

Du kan skapa en array av dem på nästan samma sätt:

Detta ger oss den första ledtråden om var functionoids är strikt sett mer kraftfulla än function-pointers: det faktum att functionoidmetoden har argument som du kan skicka till ctorerna (visas ovan som …ctor-args…) medan function-pointers-versionen inte har det. Tänk på ett functionoid-objekt som ett frystorkat funktionskall (med betoning på ordet call). Till skillnad från en pekare till en funktion är en functionoid (begreppsmässigt) en pekare till en delvis anropad funktion. Föreställ dig för ett ögonblick en teknik som låter dig skicka några men inte alla argument till en funktion och sedan låter dig frysa in det (delvis genomförda) anropet. Föreställ dig att den tekniken ger dig en slags magisk pekare tillbaka till den frystorkade, delvis fullbordade funktionsanropet. Senare skickar du de återstående args med hjälp av denna pekare, och systemet tar på magiskt sätt dina ursprungliga args (som frystorkades), kombinerar dem med eventuella lokala variabler som funktionen beräknade innan den frystorkades, kombinerar allt detta med de nyligen överförda args och fortsätter funktionens utförande där den slutade när den frystorkades. Det kanske låter som science fiction, men det är i princip vad functionoids låter dig göra. Dessutom låter de dig upprepade gånger ”slutföra” den frystorkade funktionskallan med olika ”återstående parametrar”, så ofta du vill. Plus att de tillåter (inte kräver) att du ändrar det frystorkade tillståndet när det anropas, vilket innebär att functionoids kan komma ihåg information från ett anrop till nästa.

Låt oss sätta fötterna på jorden igen och arbeta med ett par exempel för att förklara vad allt detta mumbo jumbo verkligen betyder.

Förutsatt att de ursprungliga funktionerna (i den gammaldags funktionspointer-stilen) tog lite olika parametrar.

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

När parametrarna är olika är det svårt att använda den gammaldags metoden med funktionsvisare, eftersom anroparen inte vet vilka parametrar som skall överlämnas (anroparen har bara en pekare till funktionen, inte funktionens namn eller, när parametrarna är olika, antalet och typerna av parametrar) (skriv inte ett e-postmeddelande till mig om detta; ja, det går, men man måste stå på huvudet och göra stökiga saker; men skriv inte till mig om detta – använd funktionsvisare istället).

Med functionoids är situationen, åtminstone ibland, mycket bättre. Eftersom en functionoid kan ses som ett frystorkat funktionsanrop är det bara att ta de o vanliga args, som de som jag har kallat y och/eller z, och göra dem till margs till motsvarande ctors. Du kan också skicka de gemensamma args (i det här fallet int som kallas x) till ctor, men du behöver inte göra det – du har möjlighet att skicka dem till den rent virtuella doit()-metoden istället. Jag antar att du vill skicka x till doit() och y och/eller z till ctorerna:

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

Då skapar du istället för tre funktioner tre härledda klasser:

Nu ser du att ctorernas parametrar fryses in i funktionoiden när du skapar arrayen av funktionoider:

När användaren anropar doit() på en av dessa funktionoider, anger han de ”återstående” args, och anropet kombinerar konceptuellt de ursprungliga args som lämnats till ctor med de som lämnats till doit()-metoden:

array->doit(12);

Som jag redan har antytt är en av fördelarna med funktionoider att du kan ha flera instanser av till exempel Funct1 i din array, och dessa instanser kan ha olika parametrar frystorkade i dem. Till exempel är array ocharray båda av typen Funct1, men beteendet hos array->doit(12) kommer att skilja sig från beteendet hosarray->doit(12) eftersom beteendet kommer att bero på både den 12 som överlämnades till doit() och de args som överlämnades till ctorerna.

En annan fördel med functionoider är uppenbar om vi ändrar exemplet från en array av functionoider till en localfunctionoid. För att sätta scenen, låt oss gå tillbaka till den gammaldags metoden med funktionspoängare och föreställa oss att du försöker skicka en jämförelsefunktion till en sort() eller binarySearch() rutin. Rutinen sort() eller binarySearch() kallas childRoutine() och jämförelsefunktionens pekartyp kallas FunctPtr:

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

Då skulle olika anropare skicka olika funktionspoängare beroende på vad de tyckte var bäst:

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

Vi kan enkelt översätta detta exempel till ett exempel som använder functionoids:

Med detta exempel som bakgrund kan vi se två fördelar med functionoids jämfört med function-pointers. Fördelen med ”ctor args” som beskrivs ovan, plus det faktum att functionoids kan upprätthålla tillstånd mellan anrop på ett trådsäkert sätt.Med vanliga function-pointers upprätthåller man normalt tillstånd mellan anrop via statiska data. Statiska data är dock inte trådsäkra – statiska data delas mellan alla trådar. Med funktionssymbolen får du något som i sig är trådsäkert eftersom koden slutar med trådlokala data. Genomförandet är trivialt: ändra det gammaldags statiska datumet till en instansdatamedlem i funktionoidens this-objekt, och poff, datan är inte bara trådlokal, utan den är till och med säker vid rekursiva anrop: varje anrop till yourCaller() kommer att ha ett eget distinkt Funct3-objekt med egna distinkta instansdata.

Notera att vi har vunnit något utan att förlora något. Om du vill ha trådglobala data kan funktionoider också ge ungdomarna det: det är bara att ändra det från en instansdatamedlem i funktionoidens this-objekt till en statisk datamedlem i funktionoidens klass, eller till och med till en statisk data med lokal räckvidd. Du skulle inte ha det bättre än med funktionsvisare, men du skulle inte heller ha det sämre.

Funktionsoidmetoden ger dig ett tredje alternativ som inte är tillgängligt med den gammaldags metoden: funktionoiden låter anroparna bestämma om de vill ha trådlokala eller trådglobala data. De skulle vara ansvariga för att använda lås i de fall de vill ha trådglobala data, men de skulle åtminstone ha ett val. Det är lätt:

Functionoids löser inte alla problem som man stöter på när man skapar flexibel programvara, men de är klart kraftfullare än funktionsvisare och de är värda att åtminstone utvärdera. Faktum är att man lätt kan bevisa att functionoids inte förlorar någon makt över function-pointers, eftersom man kan föreställa sig att det gammaldags tillvägagångssättet med function-pointers är likvärdigt med att ha ett globalt(!) functionoid-objekt. Eftersom man alltid kan skapa ett globalt funktionellt objekt har man inte förlorat något. QED.

Kan man göra funktionsoidobjekt snabbare än vanliga funktionsanrop?

Ja.

Om man har ett litet funktionsoidobjekt, och i den verkliga världen är det ganska vanligt, kan kostnaden för funktionsanropet vara hög i förhållande till kostnaden för det arbete som utförs av funktionsoidobjektet. I föregående FAQ implementerades funktionoider med hjälp av virtuella funktioner och kostar vanligtvis ett funktionsanrop. Ett alternativt tillvägagångssätt är att använda mallar.

Följande exempel liknar det i andan det i föregående FAQ. Jag har bytt namn på doit() till operator()() för att förbättra läsbarheten av koden för anroparen och för att göra det möjligt för någon att lämna över en vanlig funktionspoängare:

Skillnaden mellan detta tillvägagångssätt och det i föregående FAQ är att funktionspoängen ”knyts” till anroparen vid kompileringstillfället i stället för vid körningstillfället. Tänk på det som att lämna in en parameter: om du vid kompileringstid vet vilken typ av functionoid du i slutändan vill lämna in kan du använda ovanstående teknik, och du kan, åtminstone i typiska fall, få en hastighetsfördel av att låta kompilatorn inline-expandera functionoidkoden i den som anropar. Här är ett exempel:

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

När kompilatorn kompilerar ovanstående kan det hända att den inline-expanderar anropet, vilket kan förbättra prestandan.

Här är ett sätt att anropa ovanstående:

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

Bortsett från det som antyddes i första stycket ovan, kan du också skicka in namnen på normala funktioner (även om du kan ådra dig kostnaden för funktionsanropet när anroparen använder dessa):

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

Vad är skillnaden mellan en functionoid och en functor?

En functionoid är ett objekt som har en huvudmetod. Det är i princip en OO-förlängning av en C-liknande funktion som till exempel asprintf(). Man använder en functionoid när funktionen har mer än en ingångspunkt (dvs. mer än en ”metod”) och/eller behöver upprätthålla status mellan anrop på ett trådsäkert sätt (C-stilens tillvägagångssätt för att upprätthålla status mellan anrop är att lägga till en lokal ”statisk” variabel till funktionen, men det är fruktansvärt osäkert i en miljö med flera trådar).

En functor är ett specialfall av en functionoid: det är en functionoid vars metod är ”funktionsanropsoperatorn”, operator()(). Eftersom den överbelastar funktionsanropsoperatorn kan koden anropa dess huvudmetod med samma syntax som den skulle använda för ett funktionsanrop. Om t.ex. ”foo” är en funktor skulle man säga ”foo()()” för att anropa ”operator()()”-metoden på ”foo”-objektet. Fördelen med detta är i mallar, eftersom mallen då kan ha en mallparameter som kommer att användas som en funktion, och denna parameter kan vara antingen namnet på en funktion eller ett functor-objekt. Det finns en prestandafördel med att det är ett funktorobjekt eftersom metoden ”operator()()” kan inlines (medan om du överlämnar adressen till en funktion måste den nödvändigtvis vara icke-inlined).

Detta är mycket användbart för saker som ”comparison”-funktionen på sorterade behållare. I C överförs jämförelsefunktionen alltid med en pekare (t.ex, se signaturen till ”qsort()”), men i C++ kan parametern komma in antingen som en pekare till en funktion ELLER som namnet på ett funktorobjekt, och resultatet är att sorterade containrar i C++ i vissa fall kan vara mycket snabbare (och aldrig långsammare) än motsvarigheten i C.

Då Java inte har något som liknar mallar måste man använda dynamisk bindning för alla dessa saker, och dynamisk bindning innebär nödvändigtvis ett funktionsanrop. Normalt sett är det ingen stor sak, men i C++ vill vi möjliggöra extremt högpresterande kod. Det vill säga, C++ har en ”betala för det bara om du använder det”-filosofi, vilket innebär att språket aldrig godtyckligt får lägga på något överskott över vad den fysiska maskinen klarar av att utföra (naturligtvis kan en programmerare, om så önskas, använda sig av tekniker som dynamisk bindning som generellt sett kommer att lägga på ett visst överskott i utbyte mot flexibilitet eller något annat, men det är upp till konstruktören och programmeraren att avgöra om de vill ha fördelarna (och kostnaderna) med sådana konstruktioner).

Leave a Reply