2013-05-08 13 views
7

In C++, esistono due modi per passare una funzione in un'altra funzione che sembra equivalente.Funzione C++ passata come argomento modello vs parametro

#include <iostream> 

int add1(int i){ return i+1; } 
int add2(int i){ return i+2; } 

template <int (*T)(int) > 
void doTemplate(int i){ 
    std::cout << "Do Template: " << T(i) << "\n"; 
} 

void doParam(int i, int (*f)(int)){ 
    std::cout << "Do Param: " << f(i) << "\n"; 
} 

int main(){ 
    doTemplate<add1>(0); 
    doTemplate<add2>(0); 

    doParam(0, add1); 
    doParam(0, add2); 
} 

doTemplate accetta una funzione come argomento di modello, mentre doParam accetta come un puntatore a funzione, ed entrambi sembrano dare lo stesso risultato.

Quali sono i compromessi tra l'utilizzo di ciascun metodo?

+0

Provate 'doParam (0, alcune_condizioni? Add1: add2)' per vedere la differenza. –

risposta

10

La versione basata su modelli consente al compilatore di inline la chiamata, perché l'indirizzo della funzione è noto al momento della compilazione. Ovviamente, lo svantaggio è che l'indirizzo della funzione è noto a in fase di compilazione (poiché lo si utilizza come argomento del modello) e talvolta ciò potrebbe non essere possibile.

Questo ci porta al secondo caso, dove il puntatore di funzione può essere determinato solo in fase di esecuzione, rendendo quindi impossibile per il compilatore eseguire l'inlining, ma dando la flessibilità di determinare in fase di esecuzione la funzione di essere chiamato:

bool runtimeBooleanExpr = /* ... */; 
doParam(0, runtimeBooleanExpr ? add1 : add2); 

si noti, tuttavia, che ci sia una terza via:

template<typename F> 
void doParam(int i, F f){ 
    std::cout << "Do Param: " << f(i) << "\n"; 
} 

che vi dà più flessibilità e ha ancora il vantaggio di sapere al momento della compilazione quale funzione sta per essere chiamato:

doParam(0, add1); 
doParam(0, add2); 

E permette anche passando qualsiasi oggetto richiamabile invece di un puntatore a funzione:

doParam(0, my_functor()); 

int fortyTwo = 42; 
doParam(0, [=] (int i) { return i + fortyTwo; /* or whatever... */ } 

Per completezza, v'è anche un quarto modo , utilizzando std::function:

void doParam(int x, std::function<int(int)> f); 

Che ha lo stesso livello di generalità (nel senso che è possibile passare qualsiasi oggetto callable), ma consente anche per determinare l'oggetto callable in fase di esecuzione - molto probabilmente con una penalizzazione delle prestazioni, dal momento che (ancora una volta) l'inlining diventa impossibile per il compilatore.

Per un'ulteriore discussione sulle ultime due opzioni, vedere anche this Q&A on StackOverflow.

+0

potresti spiegare il '=' nel lambda? mi sto ancora fregando gli occhi :-) –

+0

@ Koushik: Cattura la variabile "fortyTwo' in base al valore. Un lambda non catturante non sarebbe stato un buon esempio, dal momento che quelli possono essere convertiti implicitamente in puntatori di funzioni;) –

+0

il [=] lo rende predefinito per catturare valore per valore (piuttosto che riferimento). SO cattura il valore 42 per quarantadue piuttosto che un riferimento ad esso. Il che significa che se gli avessi assegnato 111 in seguito, lambda avrebbe comunque utilizzato il valore 42 catturato. – jcoder

5

parametri dei modelli

  1. devono essere noti al momento della compilazione .
  2. portano a una funzione instantation per ogni valore distinto del parametro (cosiddetto modello gonfiare)
  3. la funzione chiamata è transparant al compilatore (inlining, ma potrebbe portare ad ancora più gonfiare, doppio spada taglio)
  4. funzione chiamante può essere sovraccaricato per particolari valori del parametro, senza modificare esistente codice

puntatori a funzione

  1. sono passati in fase di run .
  2. solo portare ad una funzione di chiamata (codice oggetto più piccolo)
  3. la funzione chiamata è tipicamente opaca al compilatore (senza inlining)
  4. funzione chiamante ha bisogno di un tempo di esecuzione if/switch fare cose speciali per valori speciali del parametro, questo è fragile

Quando utilizzare quale versione: se avete bisogno di velocità e un sacco di personalizzazione, utilizzare i modelli. Se hai bisogno di flessibilità in fase di esecuzione, ma non nell'implementazione, usa i puntatori di funzione.

Come @AndyProwl indica: se si dispone di un compilatore C++ 11, i puntatori di funzione sono generalizzati per richiamare oggetti come std::function e espressioni lambda. Questo apre una nuova lattina di vermi (in senso buono).

Problemi correlati