2012-02-15 7 views
6

Ho trovato alcuni buoni esempi di functor su SO come this uno, e tutti gli esempi convincenti sembrano utilizzare lo stato nella classe che definisce operator().Motivo per utilizzare questa classe stateless con una funzione call operator vs una funzione di stile c?

Mi sono imbattuto in un esempio in un libro che definisce l'operatore di chiamata di funzione senza stato, e non posso fare a meno di sentire che questo è un uso scomodo e che un normale puntatore a funzione di stile sarebbe meglio dell'uso operator() in ogni modo qui: meno codice, meno variabili (devi istanziare i comparatori), è probabilmente più efficiente a causa dell'istanza e senza perdita di significato o incapsulamento (dato che è solo una funzione).

Lo so std::sort consente di scegliere tra operator() classi e funzioni, ma ho sempre utilizzato le funzioni solo a causa della logica di cui sopra.

Quali sono i motivi per cui una classe potrebbe essere preferita?

Ecco l'esempio (parafrasato):

class Point2D { 
    //.. accessors, constructors 
    int x,y; 
}; 
class HorizComp { 
public: 
    bool operator()(const Point2D& p, const Point2D& q) const 
    { return p.getX() < q.getX(); } 
}; 

class VertComp { 
public: 
    bool operator()(const Point2D& p, const Point2D& q) const 
    { return p.getY() < q.getY(); } 
}; 

template <typename E, typename C> 
void printSmaller(const E& p, const E& q, const C& isLess) { 
    cout << (isLess(p, q) ? p : q) << endl; // print the smaller of p and q 
} 
//... 
// usage in some function: 
Point2D p(1.2, 3.2), q(1.5, 9.2); 
HorizComp horizComp; 
VertComp vorizComp; 
printSmaller(p, q, horizComp); 
printSmaller(p, q, vorizComp); 

risposta

8

Il motivo tipico è che quando si esegue questa operazione:

bool less_than(const Point&, const Point&); 
// ... 
std::sort(..., &less_than); 

L'argomento modello per il predicato è il seguente:

bool(const Point&,const Point&) 

Poiché la funzione di ordinamento riceve un puntatore a funzione, è più difficile per il compilatore per incorporare l'uso del predicato all'interno di std::sort(). Questo accade perché si potrebbe avere un'altra funzione

bool greater_than(const Point&, const Point&); 

che ha lo stesso tipo esatto, cioè l'instatiation std::sort() sarebbe condivisa tra i due predicati. (ricorda che ho detto che rende inlining più difficile, non impossibile).

Al contrario, quando si esegue questa operazione:

struct less_than { 
    bool operator()(const Point&, const Point&) const; 
}; 
// ... 
std::sort(..., less_than()); 


struct greater_than { 
    bool operator()(const Point&, const Point&) const; 
}; 
// ... 
std::sort(..., greater_than()); 

Il compilatore genera un modello di istanza unica per std::sort() per ogni predicato, rendendo più facile per inline la definizione del predicato.

+0

freddo, non ho pensato a tale proposito. Ho trovato un post sul blog che mostra il rendimento in linea in azione: http://codeforthought.blogspot.com/2011/07/performance-functors-vs-functions.html –

+0

Devo ammettere che vedo questo come un problema del compilatore. Ho visto problemi simili con inlining e il compilatore non è riuscito a de-virtualizzare le chiamate come risultato. Mi sembra che questo sia qualcosa che dovrebbe essere raggiunto da una costante propagazione ottimizzata (a patto che in questo caso sia visibile la definizione di 'less_than'). –

+0

@MatthieuM .: È sicuramente un problema con il compilatore. Ecco perché ho usato i termini "più difficile", "più facile", ecc. Non c'è una ragione fondamentale per cui il compilatore * non * genera istanze separate per entrambi i predicati come funzioni e ancora in linea i loro corpi. È solo che, per ragioni pratiche, gli implementatori del compilatore potrebbero non aver (ancora) fatto un caso speciale per questa situazione. –

5

Una ragione è l'efficienza in fase di esecuzione. Se si passa un puntatore a una funzione, il compilatore deve essere insolitamente intelligente per produrre in linea il codice per quella funzione. Passare un oggetto che definisce operator() lo rende molto più semplice per il compilatore per produrre il codice inline più semplicemente. Soprattutto per qualcosa come l'ordinamento, questo può aumentare notevolmente la velocità.

In C++ 11, un altro motivo per utilizzare una classe è per comodità: è possibile utilizzare un'espressione lambda per definire la classe.

0

Altri hanno messo in evidenza la capacità del compilatore di allineare il funtore. Un altro possibile vantaggio degli oggetti del funtore rispetto ai puntatori di funzione è la flessibilità. Il functor potrebbe essere un modello, forse una classe derivata, forse ha una configurazione run time (anche se stateless all'operatore time() è chiamato ecc.

0

Un'altra ragione è che a volte una funzione di confronto non è sufficiente.Diciamo che abbiamo un vettore di puntatori:

struct X { string name; }; 
vector<shared_ptr<X>> v; 

Ora, se vogliamo ordinare il vettore da name, dobbiamo definire la nostra predicato per la funzione sort:

struct Cmp1 
{ 
    bool operator()(const shared_ptr<X>& left, const shared_ptr<X>& right) const 
    { return left->name < right->name; } 
}; 

che è cool, ma cosa facciamo quando abbiamo bisogno di trovare gli oggetti con un nome specifico? Per lavorare con equal_range, il predicato ha bisogno di avere due diverse funzioni di confronto:

struct Cmp2 
{ 
    bool operator()(const shared_ptr<X>& left, const string& right) const 
    { return left->name < right; } 

    bool operator()(const string& left, const shared_ptr<X>& right) const 
    { return left < right->name; } 
}; 

Questo ci permette di chiamare equal_range con un oggetto string nome:

equal_range(v.begin(), v.end(), name, Cmp2()) 
Problemi correlati