Standard C++
Pointer a funzioni membro
Il tipo di “puntatore a funzione membro” è diverso dal “puntatore a funzione”?
Sì.
Considera la seguente funzione:
int f(char a, float b);
Il tipo di questa funzione è diverso a seconda che sia una funzione ordinaria o una funzione non static
membro di qualche classe:
Nota: se è una funzione static
membro di class
Fred
, il suo tipo è lo stesso come se fosse una funzione ordinaria:”int (*)(char,float)
“.
Come faccio a passare un puntatore a una funzione membro a un gestore di segnali, un callback di eventi X, una chiamata di sistema che avvia un thread/task, ecc.
Non farlo.
Perché una funzione membro non ha senso senza un oggetto su cui invocarla, non puoi farlo direttamente (se X WindowSystem fosse riscritto in C++, probabilmente passerebbe riferimenti a oggetti, non solo puntatori a funzioni; naturalmente gli oggetti incorporerebbero la funzione richiesta e probabilmente molto di più).
Come patch per il software esistente, usate una funzione di primo livello (non membro) come wrapper che prende un oggetto ottenuto attraverso qualche altra tecnica. A seconda della routine che state chiamando, questa “altra tecnica” potrebbe essere banale o potrebbe richiedere un po’ di lavoro da parte vostra. La chiamata di sistema che avvia un thread, per esempio, potrebbe richiedere di passare un puntatore di funzione insieme a un void*
, in modo da poter passare il puntatore dell’oggetto nel void*
. Molti sistemi operativi in tempo reale fanno qualcosa di simile per la funzione che avvia un nuovo compito. Nel peggiore dei casi si potrebbe memorizzare il puntatore all’oggetto in una variabile globale; questo potrebbe essere richiesto per i gestori di segnali Unix (ma le globali sono, in generale, indesiderate). In ogni caso, la funzione di primo livello chiamerebbe la funzione membro desiderata sull’oggetto.
Ecco un esempio del caso peggiore (usando una globale). Supponiamo che vogliate chiamare Fred::memberFn()
su interrupt:
Nota: le funzioni membro static
non richiedono un oggetto reale per essere invocate, sopointers-to-static
-member-functions sono solitamente compatibili con i normali pointers-to-functions. Tuttavia, anche se probabilmente funziona sulla maggior parte dei compilatori, in realtà dovrebbe essere una funzione extern "C"
non-member per essere corretta, poiché “C linkage” non copre solo cose come la manipolazione dei nomi, ma anche le convenzioni di chiamata, che potrebbero essere diverse tra C e C++.
Perché continuo a ricevere errori di compilazione (type mismatch) quando cerco di usare una funzione membro come routine di servizio ad interrupt?
Questo è un caso speciale delle due domande precedenti, quindi leggere prima le due risposte precedenti.
Le funzioni membro non static
hanno un parametro nascosto che corrisponde al puntatore this
. Il puntatore this
punta ai dati di istanza dell’oggetto. L’hardware/firmware di interrupt nel sistema non è in grado di fornire l’argomento del puntatore this
. Dovete usare funzioni “normali” (non membri di classe) o funzioni membro static
come routine di servizio d’interruzione.
Una possibile soluzione è usare un membro static
come routine di servizio d’interruzione e fare in modo che quella funzione cerchi da qualche parte la coppia istanza/membro che dovrebbe essere chiamata all’interruzione. Così l’effetto è che una funzione membro è invocata su un interrupt, ma per ragioni tecniche è necessario chiamare prima una funzione intermedia.
Perché ho problemi a prendere l’indirizzo di una funzione C++?
Risposta breve: se stai cercando di memorizzarlo in (o passarlo come) un puntatore a funzione, allora questo è il problema – questo è un corollario alla precedente FAQ.
Risposta lunga: In C++, le funzioni membro hanno un parametro implicito che punta all’oggetto (il puntatore this
all’interno della funzione membro). Si può pensare che le normali funzioni C abbiano una convenzione di chiamata diversa dalle funzioni membro, quindi i tipi dei loro puntatori (puntatore a funzione membro vs puntatore a funzione) sono diversi e incompatibili. Il C++ introduce un nuovo tipo di puntatore, chiamato puntatore-a-membro, che può essere invocato solo fornendo un oggetto.
NOTA: non tentare di “lanciare” un puntatore-a-membro-funzione in un puntatore-a-funzione; il risultato è indefinito e probabilmente disastroso. Ad esempio, non è richiesto che un puntatore a funzione membro contenga l’indirizzo macchina della funzione appropriata. Come è stato detto nell’ultimo esempio, se avete un puntatore a una normale funzione C, usate o una funzione di livello superiore (non membro), o una funzione membro static
(classe).
Come posso evitare errori di sintassi quando creo puntatori a membri?
Utilizzate un typedef
.
Sì, giusto, lo so: siete diversi. Tu sei intelligente. Puoi fare queste cose senza un typedef
. Sigh. Ho ricevuto molte e-mail da persone che, come te, hanno rifiutato di seguire il semplice consiglio di questa FAQ. Hanno sprecato ore e ore del loro tempo, quando 10 secondi di typedef
avrebbero semplificato la loro vita. Inoltre, ammettetelo, non state scrivendo codice che solo voi potete leggere; si spera che stiate scrivendo il vostro codice che anche altri saranno in grado di leggere – quando sono stanchi – quando hanno le loro scadenze e le loro sfide. Quindi perché rendere intenzionalmente la vita più difficile a se stessi e agli altri? Sii intelligente: usa un typedef
.
Ecco un esempio di classe:
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); // ...};
Il typedef è banale:
typedef int (Fred::*FredMemFn)(char x, float y); // Please do this!
Ecco! FredMemFn
è il nome del tipo, e un puntatore di quel tipo punta a qualsiasi membro di Fred
che prende(char,float)
, come f
, g
, h
e i
di Fred
.
E’ quindi banale dichiarare un puntatore a funzione membro:
int main(){ FredMemFn p = &Fred::f; // ...}
E’ anche banale dichiarare funzioni che ricevono puntatori a funzione membro:
void userCode(FredMemFn p){ /*...*/ }
E’ anche banale dichiarare funzioni che restituiscono puntatori a funzione membro:
FredMemFn userCode(){ /*...*/ }
Quindi, per favore, usate un typedef
. O questo o non mandatemi email sui problemi che avete con i vostri puntatori a funzioni membro!
Come posso evitare errori di sintassi quando chiamo una funzione membro usando un puntatore a funzione membro?
Se avete accesso a un compilatore e una libreria standard che implementa le parti appropriate del prossimo standard C++17, usate std::invoke
. Altrimenti, usate una macro #define
.
Per favore.
Per favore.
Ho ricevuto troppe e-mail da persone confuse che si sono rifiutate di seguire questo consiglio. È così semplice. Lo so, non hai bisogno distd::invoke
o di una macro, e l’esperto con cui hai parlato può farlo senza nessuno dei due, ma per favore non lasciare che il tuo ego si metta in mezzo a ciò che è importante: i soldi. Altri programmatori dovranno leggere/mantenere il tuo codice. Sì, lo so: siete più intelligenti di tutti gli altri; bene. E siete fantastici; bene. Ma non aggiungere complessità non necessarie al tuo codice.
Utilizzare std::invoke
è banale. Nota: FredMemFn
è un typedef
per un puntatore-to-membertype:
Se non puoi usare std::invoke
, riduci i costi di manutenzione usando, paradossalmente, una macro #define
in questo caso particolare.
(Normalmente non mi piacciono le macro #define
, ma dovreste usarle con i puntatori perché migliorano la leggibilità e la scrivibilità di quel tipo di codice.)
La macro è banale:
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
Anche l’uso della macro è banale. Nota: FredMemFn
è un typedef
per un puntatore a tipo membro:
La ragione per cui std::invoke
o questa macro è una buona idea è perché le invocazioni delle funzioni membro sono spesso molto più complesse del semplice esempio appena dato. La differenza in leggibilità e scrivibilità è significativa.comp.lang.c++
ha dovuto sopportare centinaia e centinaia di messaggi di programmatori confusi che non riuscivano a capire bene la sintassi. Quasi tutti questi errori sarebbero scomparsi se avessero usato std::invoke
o la macro di cui sopra.
Nota: le macro #define
sono cattive in 4 modi diversi: male#1, male#2, male#3, e male#4. Ma sono ancora utili a volte. Ma dovreste comunque provare un vago senso di vergogna dopo averle usate.
Come faccio a creare e usare un array di puntatori a membro-funzione?
Utilizza sia la typedef
che la std::invoke
o la macro #define
descritta prima, e hai finito al 90%.
Passo 1: create un typedef
:
Passo 2: create una macro #define
se non avete std::invoke
:
#define CALL_MEMBER_FN(object,ptrToMember) ((object).*(ptrToMember))
Ora il vostro array di puntatori a funzioni-membro è semplice:
FredMemFn a = { &Fred::f, &Fred::g, &Fred::h, &Fred::i };
E anche il vostro uso di uno dei puntatori alle funzioni membro è semplice:
void userCode(Fred& fred, int memFnNum){ // Assume memFnNum is between 0 and 3 inclusive: std::invoke(a, fred, 'x', 3.14);}
o se non avete 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: le macro #define
sono cattive in 4 modi diversi: male#1, male#2, male#3, e male#4. Ma sono ancora utili a volte. Vergognatevi, sentitevi in colpa, ma quando un costrutto malvagio come una macro migliora il vostro software, usatela.
Come faccio a dichiarare un puntatore a una funzione membro che punta a una funzione membro const?
Risposta breve: aggiungete un const
a destra del )
quando usate un typedef
per dichiarare il puntatore-funzione membro-tipo.
Per esempio, supponiamo che vogliate un puntatore a una funzione-membro che punti a Fred::f
, Fred::g
o Fred::h
:
class Fred {public: int f(int i) const; int g(int i) const; int h(int j) const; // ...};
Quindi, quando usate un typedef
per dichiarare il tipo membro-funzione-pointer, dovrebbe essere così:
Ecco!
Poi puoi dichiarare/passare/restituire i puntatori a funzioni membro come al solito:
Qual è la differenza tra gli operatori .* e ->*?
Non avrai bisogno di capire questo se usi std::invoke
o una macro per le chiamate a funzioni membro-pointer. Ohyea, per favore usate std::invoke
o una macro in questo caso. E ho detto che dovreste usare std::invoke
o una macro in questo caso?!?
Per esempio:
Ma considerate invece di usare un std::invoke
o una 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);}
o
Come discusso prima, le invocazioni del mondo reale sono spesso molto più complicate di quelle semplici qui, quindi usare un std::invoke
o una macro migliorerà tipicamente la scrivibilità e leggibilità del vostro codice.
Posso convertire un puntatore a una funzione membro in un void*?
No!
Dettagli tecnici: i puntatori alle funzioni membro e i puntatori ai dati non sono necessariamente rappresentati allo stesso modo. Un puntatore a una funzione membro potrebbe essere una struttura di dati piuttosto che un singolo puntatore. Pensateci: se sta puntando a una funzione virtuale, potrebbe non puntare effettivamente a un mucchio di codice risolvibile staticamente, quindi potrebbe anche non essere un indirizzo normale – potrebbe essere un’altra struttura dati di qualche tipo.
Si prega di non inviarmi e-mail se quanto sopra sembra funzionare sulla vostra particolare versione del vostro particolare compilatore sul vostro particolare sistema operativo. Non mi interessa. È illegale, punto.
Posso convertire un puntatore a funzione in un void*?
No!
Dettagli tecnici: void*
I puntatori sono puntatori a dati, e i puntatori a funzioni puntano a funzioni. Il linguaggio non richiede che le funzioni e i dati siano nello stesso spazio di indirizzamento, quindi, a titolo di esempio e non di limitazione, su architetture che li hanno in spazi di indirizzamento diversi, i due diversi tipi di puntatore non saranno comparabili.
Si prega di non inviarmi una e-mail se quanto sopra sembra funzionare sulla vostra particolare versione del vostro particolare compilatore sul vostro particolare sistema operativo. Non mi interessa. È illegale, punto.
Ho bisogno di qualcosa come i puntatori di funzioni, ma con più flessibilità e/o sicurezza dei thread; c’è un altro modo?
Usa un functionoid.
Che diavolo è un functionoid, e perché dovrei usarne uno?
I functionoid sono funzioni sotto steroidi. I functionoidi sono strettamente più potenti delle funzioni, e questo potere extra risolve alcune (non tutte) delle sfide tipicamente affrontate quando si usano i puntatori di funzione.
Facciamo un esempio che mostra un uso tradizionale dei puntatori di funzione, poi tradurremo quell’esempio in functionoidi. L’idea tradizionale dei puntatori di funzione è di avere un mucchio di funzioni compatibili:
int funct1( /*...params...*/ ) { /*...code...*/ }int funct2( /*...params...*/ ) { /*...code...*/ }int funct3( /*...params...*/ ) { /*...code...*/ }
Poi si accede a queste tramite puntatori di funzione:
typedef int(*FunctPtr)( /*...params...*/ );void myCode(FunctPtr f){ // ... f( /*...args-go-here...*/ ); // ...}
A volte le persone creano un array di questi puntatori di funzione:
FunctPtr array;array = funct1;array = funct1;array = funct3;array = funct2;// ...
In questo caso chiamano la funzione accedendo all’array:
array( /*...args-go-here...*/ );
Con i functionoid, si crea prima una classe base con un metodo puro-virtuale:
Poi invece di tre funzioni, si creano tre classi derivate:
Poi invece di passare un puntatore di funzione, si passa un Funct*
. Creerò un typedef
chiamato FunctPtr
semplicemente per rendere il resto del codice simile all’approccio vecchio stile:
typedef Funct* FunctPtr;void myCode(FunctPtr f){ // ... f->doit( /*...args-go-here...*/ ); // ...}
Puoi creare un array di queste classi quasi allo stesso modo:
Questo ci dà il primo indizio su dove i functionoid sono strettamente più potenti dei function-pointers: il fatto che l’approccio functionoid ha argomenti che potete passare ai ctors (mostrati sopra come …ctor-args…) mentre la versione function-pointers no. Pensate ad un oggetto functionoid come ad una chiamata di funzione liofilizzata (enfasi sulla parola chiamata). A differenza di un puntatore a una funzione, un functionoid è (concettualmente) un puntatore a una funzione parzialmente chiamata. Immaginate per il momento una tecnologia che vi permetta di passare alcuni ma non tutti gli argomenti ad una funzione, poi vi permetta di congelare quella chiamata (parzialmente completata). Fate finta che questa tecnologia vi restituisca una specie di puntatore magico a quella chiamata di funzione parzialmente completata e congelata. Poi più tardi passate gli argomenti rimanenti usando quel puntatore, e il sistema magicamente prende i vostri argomenti originali (che sono stati congelati), li combina con qualsiasi variabile locale che la funzione ha calcolato prima di essere congelata, combina tutto questo con gli argomenti appena passati, e continua l’esecuzione della funzione dove l’ha lasciata quando è stata congelata. Questo potrebbe sembrare fantascienza, ma è ciò che i functionoidi vi permettono di fare. Inoltre vi permettono di “completare” ripetutamente quella funzione-calcolo liofilizzata con vari “parametri rimanenti” diversi, tutte le volte che volete. Inoltre vi permettono (non richiedono) di cambiare lo stato della funzione liofilizzata quando viene chiamata, il che significa che i functionoidi possono ricordare le informazioni da una chiamata all’altra.
Riprendiamo i piedi per terra e lavoriamo su un paio di esempi per spiegare cosa significhi veramente tutto questo mumbo jumbo.
Supponiamo che le funzioni originali (nel vecchio stile funzione-pointer) prendano parametri leggermente diversi.
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...*/ }
Quando i parametri sono diversi, l’approccio vecchio stile puntatore-funzione è difficile da usare, poiché il chiamante non sa quali parametri passare (il chiamante ha solo un puntatore alla funzione, non il nome della funzione o, quando i parametri sono diversi, il numero e i tipi dei suoi parametri) (non scrivetemi un’email su questo; sì, potete farlo, ma dovete stare sulla vostra testa e fare cose sporche; ma non scrivetemi su questo – usate invece i functionoid).
Con i functionoidi, la situazione è, almeno a volte, molto migliore. Dal momento che un functionoid può essere pensato come una chiamata di funzione liofilizzata, basta prendere gli argomenti non comuni, come quelli che ho chiamato y
e/o z
, e farli diventare args ai ctors corrispondenti. Potete anche passare gli argomenti comuni (in questo caso il int
chiamato x
) al ctor, ma non dovete farlo – avete invece la possibilità di passarli al metodo puro virtuale doit()
. Suppongo che vogliate passare x
in doit()
e y
e/o z
nei ctor:
class Funct {public: virtual int doit(int x) = 0;};
Allora, invece di tre funzioni, create tre classi derivate:
Ora vedete che i parametri del ctor vengono congelati nel functionoid quando create l’array di functionoid:
Così quando l’utente invoca il doit()
su uno di questi functionoidi, fornisce gli argomenti “rimanenti”, e la chiamata combina concettualmente gli argomenti originali passati al ctor con quelli passati nel metodo doit()
:
array->doit(12);
Come ho già accennato, uno dei vantaggi dei functionoidi è che potete avere diverse istanze di, diciamo, Funct1
nel vostro array, e queste istanze possono avere diversi parametri congelati in esse. Per esempio, array
earray
sono entrambi di tipo Funct1
, ma il comportamento di array->doit(12)
sarà diverso da quello diarray->doit(12)
poiché il comportamento dipenderà sia dal 12 che è stato passato a doit()
che dagli argomenti passati ai ctors.
Un altro vantaggio dei functionoidi è evidente se cambiamo l’esempio da un array di functionoidi a un functionoide locale. Per preparare la scena, torniamo al vecchio approccio funzione-puntatore, e immaginiamo che si stia cercando di passare una funzione di confronto a una routine sort()
o binarySearch()
. La sort()
o binarySearch()
routine si chiama childRoutine()
e il tipo di funzione-pointer di confronto si chiama FunctPtr
:
void childRoutine(FunctPtr f){ // ... f( /*...args...*/ ); // ...}
Quindi i diversi chiamanti passerebbero diversi puntatori di funzioni a seconda di ciò che pensano sia meglio:
void myCaller(){ // ... childRoutine(funct1); // ...}void yourCaller(){ // ... childRoutine(funct3); // ...}
Possiamo facilmente tradurre questo esempio in uno che usa i functionoid:
Dato questo esempio come sfondo, possiamo vedere due benefici dei functionoid rispetto ai function-pointers. Il beneficio del “ctor args” descritto sopra, più il fatto che i functionoidi possono mantenere lo stato tra le chiamate in modo thread-safe.Con i semplici function-pointers, le persone normalmente mantengono lo stato tra le chiamate tramite dati statici. Tuttavia i dati statici non sono intrinsecamente thread-safe – i dati statici sono condivisi tra tutti i thread. L’approccio functionoid vi fornisce qualcosa che è intrinsecamente thread-safe poiché il codice finisce con dati thread-local. L’implementazione è banale: cambiate il vecchio dato statico in un membro di dati di istanza all’interno dell’oggetto this
del functionoid, e puf, i dati non solo sono thread-local, ma sono anche sicuri con le chiamate ricorsive: ogni chiamata a yourCaller()
avrà il suo distinto oggetto Funct3
con i suoi distinti dati di istanza.
Nota che abbiamo guadagnato qualcosa senza perdere nulla. Se volete dati globali per il thread, anche i functionoid possono darvi giovani: basta cambiarli da un membro di dati di istanza all’interno dell’oggetto this
del functionoid a un membro di dati statici all’interno della classe del functionoid, o anche a un dato statico di portata locale. Non stareste meglio che con i puntatori di funzione, ma non stareste neanche peggio.
L’approccio functionoid vi dà una terza opzione che non è disponibile con l’approccio vecchio stile: il functionoid lascia i chiamanti decidere se vogliono dati thread-local o thread-global. Sarebbero responsabili di usare i blocchi nei casi in cui vogliono i dati globali del thread, ma almeno avrebbero la possibilità di scegliere. È facile:
I functionoid non risolvono tutti i problemi che si incontrano nel fare software flessibile, ma sono strettamente più potenti dei puntatori di funzioni e vale la pena almeno valutarli. Infatti si può facilmente dimostrare che i functionoidi non perdono alcun potere rispetto ai function-pointers, poiché si può immaginare che l’approccio vecchio stile dei function-pointers sia equivalente ad avere un oggetto functionoid globale(!). Dato che potete sempre fare un oggetto functionoid globale, non avete perso alcun terreno. QED.
Può rendere i functionoid più veloci delle normali chiamate di funzione?
Sì.
Se avete un piccolo functionoid, e nel mondo reale questo è piuttosto comune, il costo della chiamata di funzione può essere alto rispetto al costo del lavoro fatto dal functionoid. Nelle FAQ precedenti, i functionoidi erano implementati usando funzioni virtuali e tipicamente vi costeranno una function-call. Un approccio alternativo usa i template.
L’esempio seguente è simile nello spirito a quello della precedente FAQ. Ho rinominato doit()
inoperator()()
per migliorare la leggibilità del codice del chiamante e per permettere a qualcuno di passare un normale puntatore di funzione:
La differenza tra questo approccio e quello nella precedente FAQ è che il fuctionoid viene “legato” al chiamante a tempo di compilazione piuttosto che a tempo di esecuzione. Pensatelo come il passaggio di un parametro: se sapete a tempo di compilazione il tipo di functionoid che volete passare, allora potete usare la tecnica di cui sopra, e potete, almeno in casi tipici, ottenere un beneficio di velocità dall’avere il compilatore che espande in linea il codice del functionoid all’interno del chiamante. Ecco un esempio:
template <typename FunctObj>void myCode(FunctObj f){ // ... f( /*...args-go-here...*/ ); // ...}
Quando il compilatore compila quanto sopra, potrebbe espandere in linea la chiamata che potrebbe migliorare le prestazioni.
Ecco un modo per chiamare quanto sopra:
void blah(){ // ... Funct2 x("functionoids are powerful", 42); myCode(x); // ...}
A parte: come è stato accennato nel primo paragrafo sopra, si possono anche passare i nomi delle funzioni normali (anche se si può incorrere nel costo della chiamata di funzione quando il chiamante le usa):
void myNormalFunction(int x);void blah(){ // ... myCode(myNormalFunction); // ...}
Qual è la differenza tra un functionoid e un functor?
Un functionoid è un oggetto che ha un metodo principale. È fondamentalmente l’estensione OO di una funzione di tipo C come asprintf(). Si usa un functionoid ogni volta che la funzione ha più di un punto di ingresso (cioè più di un “metodo”) e/o ha bisogno di mantenere lo stato tra le chiamate in modo sicuro per i thread (l’approccio in stile C per mantenere lo stato tra le chiamate è quello di aggiungere una variabile locale “statica” alla funzione, ma questo è terribilmente insicuro in un ambiente multi-thread).
Un functor è un caso speciale di un functionoid: è un functionoid il cui metodo è il “function-call operator, “operator()(). Poiché sovraccarica l’operatore di chiamata di funzione, il codice può chiamare il suo metodo principale usando la stessa sintassi di una chiamata di funzione. Ad esempio, se “pippo” è un funtore, per chiamare il metodo “operator()()” sull’oggetto “pippo” si dirà “pippo()”. Il vantaggio di questo è nei template, poiché allora il template può avere un parametro template che sarà usato come una funzione, e questo parametro può essere sia il nome di una funzione che un oggetto funtore. C’è un vantaggio in termini di prestazioni nell’essere un oggetto functor, poiché il metodo “operator()()” può essere delineato (mentre se si passa l’indirizzo di una funzione deve, necessariamente, essere non delineato).
Questo è molto utile per cose come la funzione “comparison” sui contenitori ordinati. In C, la funzione di confronto è sempre passata per puntatore (es, si veda la firma di “qsort()”), ma in C++ il parametro può arrivare sia come puntatore alla funzione OPPURE come nome di un oggetto-funzione, e il risultato è che i contenitori ordinati in C++ possono essere, in alcuni casi, molto più veloci (e mai più lenti) dell’equivalente in C.
Siccome Java non ha nulla di simile ai template, deve usare il binding dinamico per tutte queste cose, e il binding dinamico significa necessariamente una chiamata di funzione. Normalmente non è un grosso problema, ma in C++ vogliamo abilitare un codice ad altissime prestazioni. Cioè, il C++ ha una filosofia del tipo “pagalo solo se lo usi”, il che significa che il linguaggio non deve mai imporre arbitrariamente alcun sovraccarico rispetto a quello che la macchina fisica è in grado di eseguire (naturalmente un programmatore può, opzionalmente, usare tecniche come il binding dinamico che, in generale, imporrà qualche sovraccarico in cambio della flessibilità o di qualche altra “ilità”, ma sta al progettista e al programmatore decidere se vogliono i benefici (e i costi) di tali costrutti).
.
Leave a Reply