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


Sigo escuchando mucho sobre functors en C ++. ¿Puede alguien darme una visión general de lo que son y en qué casos serían útiles?


704
2017-12-10 17:47


origen


Respuestas:


Un functor es prácticamente una clase que define el operador (). Eso le permite crear objetos que "se parecen" a una función:

// this is a functor
struct add_x {
  add_x(int x) : x(x) {}
  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 sobre funtores. Una es que a diferencia de las funciones regulares, pueden contener estado. El ejemplo anterior crea una función que agrega 42 a lo que sea que le des. Pero ese valor 42 no está codificado, se especificó como un argumento de constructor cuando creamos nuestra instancia de functor. Pude crear otro sumador, que agregó 27, simplemente llamando al constructor con un valor diferente. Esto los hace muy personalizables.

Como muestran las últimas líneas, a menudo se pasan los funtores como argumentos a otras funciones como std :: transform o los otros algoritmos estándar de la biblioteca. Podrías hacer lo mismo con un puntero de función normal, excepto que, como dije antes, los funtores pueden "personalizarse" porque contienen estado, haciéndolos 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 funtor es general y agrega cualquier cosa con la que lo hayas inicializado, y también es potencialmente más eficiente. En el ejemplo anterior, el compilador sabe exactamente qué función std::transform debería llamar. Debería llamar add_x::operator(). Eso significa que puede alinear esa llamada de función. Y eso lo hace tan eficiente como si hubiera llamado manualmente la función en cada valor del vector.

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


857
2017-12-10 17:58



Pequeña adición. Puedes usar boost::function, para crear funtores 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 funtor

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 la función boost :: bind and boost :: puede crear un functor a partir del método de clase, en realidad 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"

Puede crear una lista o vector de funtores

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


111
2017-12-10 19:15



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)
{ ....}

73
2017-12-10 17:58



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

Se usa en lugar de la función simple:

caracteristicas:

  • Objeto de función puede tener estado
  • El objeto de función encaja en OOP (se comporta como cualquier otro objeto).

Contras:

  • Trae más complejidad al programa.

Se utiliza en lugar del puntero a la función:

caracteristicas:

  • El objeto de función a menudo puede estar en línea

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 da un poco de sobrecarga)

Se usa en lugar de la función virtual:

caracteristicas:

  • El objeto de función (no virtual) no requiere el envío de vtable y runtime, 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 da un poco de sobrecarga)

37
2017-11-21 16:59



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

Los funtores se usan comúnmente en algoritmos STL. Son útiles porque pueden mantener el estado antes y después de llamadas a funciones, como un cierre en lenguajes funcionales. Por ejemplo, podrías definir un MultiplyBy Functor que multiplica su argumento por una cantidad especificada:

class MultiplyBy {
private:
    int factor;

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

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

Entonces podrías pasar un MultiplyBy objetar 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 funtor sobre un puntero a una función es que la llamada puede incluirse en más casos. Si pasó un puntero a la función para transform, a no ser que ese la llamada se ha ingresado y el compilador sabe que siempre le pasa la misma función, no puede alinear la llamada a través del puntero.


34
2017-12-10 18:10



Para los novatos como yo, entre nosotros: después de investigar un poco, descubrí lo que hacía el código jalf.

Un functor es un objeto clase o estructura que se puede "llamar" como una función. Esto es posible al sobrecargar el () operator. los () operator (no estoy seguro de cómo se llama) puede tomar cualquier cantidad de argumentos. Otros operadores solo toman dos, es decir, + operator solo puede tomar dos valores (uno a cada lado del operador) y devolver el valor sobre el que lo haya sobrecargado. Puede caber cualquier cantidad de argumentos dentro de un () operator que es lo que le da su flexibilidad.

Para crear un functor primero creas tu clase. Luego, crea un constructor para la clase con un parámetro de su elección de tipo y nombre. Esto es seguido en la misma declaración por una lista de inicializadores (que usa un solo operador de dos puntos, algo en lo que yo también era nuevo) que construye los objetos de miembro de clase con el parámetro previamente declarado para el constructor. Entonces el () operator está sobrecargado Finalmente declaras los objetos privados de la clase o estructura que has creado.

Mi código (encontré que los nombres de las variables de jalf son confusos)

class myFunctor
{ 
    public:
        /* myFunctor is the constructor. parameterVar is the parameter passed to
           the constructor. : is the initializer list operator. myObject is the
           private member object of the myFunctor class. parameterVar is passed
           to the () operator which takes it and adds it to myObject in the
           overloaded () operator function. */
        myFunctor (int parameterVar) : myObject( parameterVar ) {}

        /* the "operator" word is a keyword which indicates this function is an 
           overloaded operator function. The () following this just tells the
           compiler that () is the operator being overloaded. Following that is
           the parameter for the overloaded operator. This parameter is actually
           the argument "parameterVar" passed by the constructor we just wrote.
           The last part of this statement is the overloaded operators body
           which adds the parameter passed to the member object. */
        int operator() (int myArgument) { return myObject + myArgument; }

    private: 
        int myObject; //Our private member object.
}; 

Si algo de esto es impreciso o simplemente incorrecto, ¡no dude en corregirme!


28
2018-01-04 20:06



Un functor es un función de orden superior que aplica una función a los tipos parametrizados (es decir, con plantilla). Es una generalización de la mapa función de orden superior. Por ejemplo, podríamos definir un functor para std::vector Me gusta esto:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::vector<U> fmap(F f, const std::vector<T>& vec)
{
    std::vector<U> result;
    std::transform(vec.begin(), vec.end(), std::back_inserter(result), f);
    return result;
}

Esta función toma una std::vector<T> y devoluciones std::vector<U> cuando se le da una función F eso toma una T y devuelve un U. Un funtor no tiene que definirse en los tipos de contenedor, también se puede definir para cualquier tipo de plantilla, incluidos std::shared_ptr:

template<class F, class T, class U=decltype(std::declval<F>()(std::declval<T>()))>
std::shared_ptr<U> fmap(F f, const std::shared_ptr<T>& p)
{
    if (p == nullptr) return nullptr;
    else return std::shared_ptr<U>(new U(f(*p)));
}

Aquí hay un ejemplo simple que convierte el tipo en double:

double to_double(int x)
{
    return x;
}

std::shared_ptr<int> i(new int(3));
std::shared_ptr<double> d = fmap(to_double, i);

std::vector<int> is = { 1, 2, 3 };
std::vector<double> ds = fmap(to_double, is);

Hay dos leyes que los funtores deberían seguir. La primera es la ley de identidad, que establece que si al functor se le asigna una función de identidad, debería ser lo mismo que aplicar la función de identidad al tipo, es decir fmap(identity, x) debe ser el mismo que identity(x):

struct identity_f
{
    template<class T>
    T operator()(T x) const
    {
        return x;
    }
};
identity_f identity = {};

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<int> is1 = fmap(identity, is);
std::vector<int> is2 = identity(is);

La siguiente ley es la ley de composición, que establece que si al functor se le asigna una composición de dos funciones, debería ser lo mismo que aplicar el functor para la primera función y luego nuevamente para la segunda función. Asi que, fmap(std::bind(f, std::bind(g, _1)), x) debe ser el mismo que fmap(f, fmap(g, x)):

double to_double(int x)
{
    return x;
}

struct foo
{
    double x;
};

foo to_foo(double x)
{
    foo r;
    r.x = x;
    return r;
}

std::vector<int> is = { 1, 2, 3 };
// These two statements should be equivalent.
// is1 should equal is2
std::vector<foo> is1 = fmap(std::bind(to_foo, std::bind(to_double, _1)), is);
std::vector<foo> is2 = fmap(to_foo, fmap(to_double, is));

16
2018-05-06 05:47



Aquí hay una situación real en la que me vi obligado a usar un Functor para resolver mi problema:

Tengo un conjunto de funciones (digamos, 20 de ellas), y todas son idénticas, excepto que cada una llama a una función específica diferente en 3 puntos específicos.

Esto es desperdicio increíble y duplicación de código. Normalmente me gustaría pasar un puntero a la función, y simplemente llamar a eso en los 3 puntos. (Por lo tanto, el código solo debe aparecer una vez, en lugar de veinte veces).

Pero luego me di cuenta, en cada caso, que la función específica requería un perfil de parámetros completamente diferente. Algunas veces 2 parámetros, a veces 5 parámetros, etc.

Otra solución sería tener una clase base, donde la función específica es un método reemplazado en una clase derivada. ¿Pero realmente quiero construir toda esta HERENCIA, solo para poder pasar un puntero a la función?

SOLUCIÓN: Entonces, lo que hice fue crear una clase contenedora (un "Functor") que puede invocar cualquiera de las funciones que necesitaba. Lo configuré por adelantado (con sus parámetros, etc.) y luego lo transfiero en lugar de un puntero a la función. Ahora el código llamado puede disparar el Functor, sin saber qué está sucediendo en el interior. Incluso puede llamarlo varias veces (lo necesitaba para llamar 3 veces).


Eso es todo, un ejemplo práctico donde un Functor resultó ser la solución obvia y fácil, que me permitió reducir la duplicación de código de 20 funciones a 1.


8
2017-12-26 06:54



A excepción de los utilizados en la devolución de llamada, los funtores de C ++ también pueden ayudar a proporcionar un Matlab Me gusta el estilo de acceso a un matriz  clase. Hay un ejemplo.


2
2018-03-07 07:03



Los funtores se usan en gtkmm para conectar un botón GUI a una función o método real de C ++.


Si usa la biblioteca pthread para hacer que su aplicación sea multiproceso, Functors puede ayudarlo.
Para comenzar un hilo, uno de los argumentos de pthread_create(..) es el puntero a la función que se ejecutará en su propio hilo.
 Pero hay un inconveniente. Este puntero no puede ser un puntero a un método, a menos que sea un método estático, o a menos que especifica su clase, me gusta class::method. Y otra cosa, la interfaz de su método solo puede ser:

void* method(void* something)

Entonces no puedes ejecutar (de una manera obvia) métodos de tu clase en un hilo sin hacer algo extra.

Una muy buena manera de tratar con los hilos en C ++, es crear su propio Thread clase. Si quería ejecutar métodos de MyClass clase, lo que hice fue transformar esos métodos en Functor clases derivadas

También el Thread la clase tiene este método: static void* startThread(void* arg)
Un puntero a este método se usará como argumento para llamar pthread_create(..). Y qué startThread(..) debe recibir en arg es una void* referencia hecha a una instancia en el montón de cualquier Functor clase derivada, que se restituirá a Functor* cuando se ejecuta, y luego se llama es run() método.


1
2017-10-06 04:44



Para agregar, he usado objetos de función para adaptar un método heredado existente al patrón de comando; (único lugar donde se sentía la belleza del paradigma OO verdadero OCP);  También agregando aquí el patrón de adaptador de función relacionado.

Supongamos que su método tiene la firma:

int CTask::ThreeParameterTask(int par1, int par2, int par3)

Veremos cómo podemos ajustarlo para el patrón de comando; para esto, primero, debe escribir un adaptador de función miembro para poder llamarlo como un objeto de función.

Nota: esto es feo, y puede ser que puedas usar los ayudantes de enlace de Boost, etc., pero si no puedes o no quieres hacerlo, esta es una manera.

// a template class for converting a member function of the type int        function(int,int,int)
//to be called as a function object
template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
class mem_fun3_t
{
  public:
explicit mem_fun3_t(_Ret (_Class::*_Pm)(_arg1,_arg2,_arg3))
    :m_Ptr(_Pm) //okay here we store the member function pointer for later use
    {}

//this operator call comes from the bind method
_Ret operator()(_Class *_P, _arg1 arg1, _arg2 arg2, _arg3 arg3) const
{
    return ((_P->*m_Ptr)(arg1,arg2,arg3));
}
private:
_Ret (_Class::*m_Ptr)(_arg1,_arg2,_arg3);// method pointer signature
};

Además, necesitamos un método de ayuda mem_fun3 para la clase anterior para ayudar en las llamadas.

template<typename _Ret,typename _Class,typename _arg1,typename _arg2,typename _arg3>
mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3> mem_fun3 ( _Ret (_Class::*_Pm)          (_arg1,_arg2,_arg3) )
{
  return (mem_fun3_t<_Ret,_Class,_arg1,_arg2,_arg3>(_Pm));

}

Ahora, para vincular los parámetros, tenemos que escribir una función de enlace. Entonces, aquí va:

template<typename _Func,typename _Ptr,typename _arg1,typename _arg2,typename _arg3>
class binder3
{
public:
//This is the constructor that does the binding part
binder3(_Func fn,_Ptr ptr,_arg1 i,_arg2 j,_arg3 k)
    :m_ptr(ptr),m_fn(fn),m1(i),m2(j),m3(k){}

 //and this is the function object 
 void operator()() const
 {
        m_fn(m_ptr,m1,m2,m3);//that calls the operator
    }
private:
    _Ptr m_ptr;
    _Func m_fn;
    _arg1 m1; _arg2 m2; _arg3 m3;
};

Y, una función auxiliar para usar la clase binder3 - bind3:

//a helper function to call binder3
template <typename _Func, typename _P1,typename _arg1,typename _arg2,typename _arg3>
binder3<_Func, _P1, _arg1, _arg2, _arg3> bind3(_Func func, _P1 p1,_arg1 i,_arg2 j,_arg3 k)
{
    return binder3<_Func, _P1, _arg1, _arg2, _arg3> (func, p1,i,j,k);
}

Ahora, tenemos que usar esto con la clase Command; use el siguiente typedef:

typedef binder3<mem_fun3_t<int,T,int,int,int> ,T* ,int,int,int> F3;
//and change the signature of the ctor
//just to illustrate the usage with a method signature taking more than one parameter
explicit Command(T* pObj,F3* p_method,long timeout,const char* key,
long priority = PRIO_NORMAL ):
m_objptr(pObj),m_timeout(timeout),m_key(key),m_value(priority),method1(0),method0(0),
method(0)
{
    method3 = p_method;
}

Así es como lo llamas:

F3 f3 = PluginThreadPool::bind3( PluginThreadPool::mem_fun3( 
      &CTask::ThreeParameterTask), task1,2122,23 );

Nota: f3 (); llamará al método task1-> ThreeParameterTask (21,22,23) ;.

El contexto completo de este patrón en el siguiente enlazar


1
2018-01-28 06:50