¿Qué son los functores de C++ y sus usos?

Resuelto Konrad asked hace 15 años • 14 respuestas

Sigo escuchando mucho sobre functores en C++. ¿Alguien puede darme una visión general de qué son y en qué casos serían útiles?

Konrad avatar Dec 11 '08 00:12 Konrad
Aceptado

Un funtor es prácticamente una clase que define el operator(). Eso te permite crear objetos que "parecen" una función:

// this is a functor
struct add_x {
  add_x(int val) : x(val) {}  // Constructor
  int operator()(int y) const { return x + y; }

private:
  int x;
};

// Now you can use it like this:
add_x add42(42); // create an instance of the functor class
int i = add42(8); // and "call" it
assert(i == 50); // and it added 42 to its argument

std::vector<int> in; // assume this contains a bunch of values)
std::vector<int> out(in.size());
// Pass a functor to std::transform, which calls the functor on every element 
// in the input sequence, and stores the result to the output sequence
std::transform(in.begin(), in.end(), out.begin(), add_x(1)); 
assert(out[i] == in[i] + 1); // for all i

Hay un par de cosas buenas acerca de los functores. Una es que, a diferencia de las funciones regulares, pueden contener estado. El ejemplo anterior crea una función que suma 42 a lo que le des. Pero ese valor 42 no está codificado, se especificó como argumento del constructor cuando creamos nuestra instancia de funtor. Podría crear otro sumador, que sumaría 27, simplemente llamando al constructor con un valor diferente. Esto los hace muy personalizables.

Como muestran las últimas líneas, a menudo se pasan functores como argumentos a otras funciones como std::transform u otros algoritmos de biblioteca estándar. Podrías hacer lo mismo con un puntero de función normal excepto que, como dije anteriormente, los funtores se pueden "personalizar" porque contienen estado, lo que los hace más flexibles (si quisiera usar un puntero de función, tendría que escribir una función que agregó exactamente 1 a su argumento. El functor es general y agrega cualquier cosa con la que lo inicializó), y también son potencialmente más eficientes. En el ejemplo anterior, el compilador sabe exactamente qué función std::transformdebe llamar. Debería llamar add_x::operator(). Eso significa que puede incorporar esa llamada a función. Y eso lo hace tan eficiente como si hubiera llamado manualmente a la función en cada valor del vector.

Si hubiera pasado un puntero de función, el compilador no podría ver inmediatamente a qué función apunta, por lo que, a menos que realice algunas optimizaciones globales bastante complejas, tendría que desreferenciar el puntero en tiempo de ejecución y luego realizar la llamada.

jalf avatar Dec 10 '2008 17:12 jalf

Pequeña adición. Puedes usar boost::function, para crear functores a partir de funciones y métodos, como este:

class Foo
{
public:
    void operator () (int i) { printf("Foo %d", i); }
};
void Bar(int i) { printf("Bar %d", i); }
Foo foo;
boost::function<void (int)> f(foo);//wrap functor
f(1);//prints "Foo 1"
boost::function<void (int)> b(&Bar);//wrap normal function
b(1);//prints "Bar 1"

y puedes usar boost::bind para agregar estado a este functor

boost::function<void ()> f1 = boost::bind(foo, 2);
f1();//no more argument, function argument stored in f1
//and this print "Foo 2" (:
//and normal function
boost::function<void ()> b1 = boost::bind(&Bar, 2);
b1();// print "Bar 2"

y lo más útil, con boost::bind y boost::function puedes crear un functor a partir del método de clase, en realidad este es un delegado:

class SomeClass
{
    std::string state_;
public:
    SomeClass(const char* s) : state_(s) {}

    void method( std::string param )
    {
        std::cout << state_ << param << std::endl;
    }
};
SomeClass *inst = new SomeClass("Hi, i am ");
boost::function< void (std::string) > callback;
callback = boost::bind(&SomeClass::method, inst, _1);//create delegate
//_1 is a placeholder it holds plase for parameter
callback("useless");//prints "Hi, i am useless"

Puedes crear una lista o un vector de functores.

std::list< boost::function<void (EventArg e)> > events;
//add some events
....
//call them
std::for_each(
        events.begin(), events.end(), 
        boost::bind( boost::apply<void>(), _1, e));

Hay un problema con todo esto: los mensajes de error del compilador no son legibles por humanos :)

Evgeny Lazin avatar Dec 10 '2008 19:12 Evgeny Lazin

Un Functor es un objeto que actúa como una función. Básicamente, una clase que define operator().

class MyFunctor
{
   public:
     int operator()(int x) { return x * 2;}
}

MyFunctor doubler;
int x = doubler(5);

La verdadera ventaja es que un functor puede mantener el estado.

class Matcher
{
   int target;
   public:
     Matcher(int m) : target(m) {}
     bool operator()(int x) { return x == target;}
}

Matcher Is5(5);

if (Is5(n))    // same as if (n == 5)
{ ....}
James Curran avatar Dec 10 '2008 17:12 James Curran

El nombre "functor" se ha utilizado tradicionalmente en la teoría de categorías mucho antes de que C++ apareciera en escena. Esto no tiene nada que ver con el concepto de funtor de C++. Es mejor usar el nombre del objeto de función en lugar de lo que llamamos "functor" en C++. Así llaman otros lenguajes de programación a construcciones similares.

Usado en lugar de función simple:

Características:

  • El objeto de función puede tener estado
  • El objeto de función encaja en la programación orientada a objetos (se comporta como cualquier otro objeto).

Contras:

  • Aporta más complejidad al programa.

Usado en lugar de puntero de función:

Características:

  • El objeto de función a menudo puede estar integrado

Contras:

  • El objeto de función no se puede intercambiar con otro tipo de objeto de función durante el tiempo de ejecución (al menos a menos que extienda alguna clase base, lo que por lo tanto genera cierta sobrecarga)

Usado en lugar de función virtual:

Características:

  • El objeto de función (no virtual) no requiere vtable ni distribución en tiempo de ejecución, por lo que es más eficiente en la mayoría de los casos.

Contras:

  • El objeto de función no se puede intercambiar con otro tipo de objeto de función durante el tiempo de ejecución (al menos a menos que extienda alguna clase base, lo que por lo tanto genera cierta sobrecarga)
mip avatar Nov 21 '2009 16:11 mip

Como otros han mencionado, un funtor es un objeto que actúa como una función, es decir, sobrecarga el operador de llamada de función.

Los functores se utilizan comúnmente en algoritmos STL. Son útiles porque pueden mantener el estado antes y entre llamadas a funciones, como un cierre en los lenguajes funcionales. Por ejemplo, podrías definir un MultiplyByfuntor que multiplique su argumento por una cantidad específica:

class MultiplyBy {
private:
    int factor;

public:
    MultiplyBy(int x) : factor(x) {
    }

    int operator () (int other) const {
        return factor * other;
    }
};

Entonces podrías pasar un MultiplyByobjeto a un algoritmo como std::transform:

int array[5] = {1, 2, 3, 4, 5};
std::transform(array, array + 5, array, MultiplyBy(3));
// Now, array is {3, 6, 9, 12, 15}

Otra ventaja de un functor sobre un puntero a una función es que la llamada se puede insertar en más casos. Si pasó un puntero de función a transform, a menos que esa llamada se inserte y el compilador sepa que siempre le pasa la misma función, no puede insertar la llamada a través del puntero.

Matthew Crumley avatar Dec 10 '2008 18:12 Matthew Crumley