2012-02-15 6 views
6

He encontrado algunos buenos ejemplos de functors en SO como this uno, y todos los ejemplos convincentes parecen usar el estado en la clase que define operator().Motivo para utilizar esta clase sin estado con un operador de llamada a función vs una función de estilo c?

Encontré un ejemplo en un libro que define el operador de llamada a función sin tener estado, y no puedo evitar sentir que esto es un uso incómodo, y que un puntero de función de estilo normal, sería mejor que usar operator() en todos los sentidos aquí - menos código, menos variables (tiene que crear instancias de los comparadores), es probablemente más eficiente debido a la instanciación, y no hay pérdida de significado o encapsulación (ya que es solo una función).

std::sort le permite elegir entre las clases y funciones de operator(), pero siempre acabo de utilizar las funciones debido a la lógica anterior.

¿Cuáles son las razones por las que una clase podría preferirse?

Aquí está el ejemplo (parafraseado):

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

Respuesta

8

La razón típica es que cuando se hace esto:

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

El argumento de plantilla para el predicado es la siguiente:

bool(const Point&,const Point&) 

Dado que la función de ordenación recibe un puntero de función, es más difícil para el compilador para alinear el uso del predicado dentro de std::sort(). Esto sucede porque usted podría tener otra función

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

que tiene exactamente el mismo tipo, es decir, el std::sort() instatiation sería compartido entre los dos predicados. (recuerde que dije que hace que la alineación sea más difícil, no imposible).

Por el contrario, cuando se hace esto:

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

El compilador genera una instanciación de plantilla única para std::sort() para cada predicado, por lo que es más fácil de inline definición del predicado.

+0

fresco, yo no pienso en eso. Encontré una publicación de blog que muestra el rendimiento en línea en acción: http://codeforthought.blogspot.com/2011/07/performance-functors-vs-functions.html –

+0

Debo admitir que veo esto como un problema de compilación. He visto problemas similares con la creación de líneas y el compilador no pudo desvirtualizar las llamadas como resultado. Me parece que esto es algo que debe lograr una propagación constante pulida (siempre que, en este caso, la definición de 'less_than' sea visible). –

+0

@MatthieuM .: Definitivamente es un problema de compilación. Es por eso que utilicé los términos "más difícil", "más fácil", etc. No hay una razón fundamental por la que el compilador * no * genere instancias separadas para ambos predicados como funciones y todavía en línea sus cuerpos. Es solo que, por razones prácticas, los implementadores del compilador podrían no haber (todavía) hecho un caso especial para esta situación. –

5

Una de las razones es la eficiencia en tiempo de ejecución. Si pasa un puntero a una función, el compilador tiene que ser inusualmente inteligente para producir código para esa función en línea. Pasar un objeto que define operator() lo hace mucho más fácil para el compilador para producir el código en línea. Especialmente para algo como ordenar, esto puede aumentar la velocidad de manera bastante sustancial.

En C++ 11, otra razón para usar una clase es por conveniencia: puede usar una expresión lambda para definir la clase.

0

Otros han hecho buenos comentarios sobre la capacidad del compilador para alinear el functor. Otra ventaja posible de los objetos del functor frente a los indicadores de función es la flexibilidad. El funtor puede ser una plantilla, tal vez una clase derivada, tal vez tiene configuración de tiempo de ejecución (incluso si se llama apátrida en el momento operador() etc.

0

Otra razón es que a veces una función de comparación no es suficiente.Digamos que tenemos un vector de punteros:

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

Ahora bien, si queremos resolver el vector por name, tenemos que definir nuestro propio predicado de la función sort:

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

Eso está bien, pero ¿Qué hacemos cuando necesitamos encontrar los objetos con un nombre específico? Para trabajar con equal_range, el predicado tiene que tener dos funciones diferentes de comparación:

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

Esto nos permite llamar a equal_range con un objeto string nombre:

equal_range(v.begin(), v.end(), name, Cmp2()) 
Cuestiones relacionadas