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

876

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

Konrad
fuente
44
Este tema se ha cubierto en respuesta a esta pregunta: stackoverflow.com/questions/317450/why-override-operator#317528
Luc Touraille
2
Se utiliza para crear un cierre en C ++.
copper.hat
Mirando las respuestas a continuación, si alguien se pregunta qué operator()(...)significa: está sobrecargando el operador de "llamada de función" . Es simplemente una sobrecarga del ()operador para el operador. No se confunda operator()con llamar a una función llamada operator, pero considérela como la sintaxis habitual de sobrecarga del operador.
zardosht

Respuestas:

1041

Un functor es prácticamente solo una clase que define el operador (). Eso le 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 sobre los functores. Una es que, a diferencia de las funciones regulares, pueden contener estados. 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. Podría crear otro sumador, que agregó 27, simplemente llamando al constructor con un valor diferente. Esto los hace muy bien personalizables.

Como muestran las últimas líneas, a menudo pasa los functores como argumentos a otras funciones como std :: transform u otros algoritmos de biblioteca estándar. Puede hacer lo mismo con un puntero de función normal, excepto que, como dije anteriormente, los functores pueden ser "personalizados" 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 functor es general y agrega lo que haya inicializado), y también son potencialmente más eficientes. En el ejemplo anterior, el compilador sabe exactamente qué función std::transformdebe llamar. Debería llamaradd_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 de función en su lugar, el compilador no podría ver de inmediato 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
fuente
32
¿Puede explicar esta línea? Por favor, std :: transform (in.begin (), in.end (), out.begin (), add_x (1)); ¿Por qué escribes allí add_x, no add42?
Alecs
102
@Alecs Ambos habrían funcionado (pero el efecto habría sido diferente). Si hubiera usado add42, habría usado el functor que creé anteriormente, y habría agregado 42 a cada valor. Con add_x(1)Creo una nueva instancia del functor, una que solo agrega 1 a cada valor. Es simplemente para mostrar que, a menudo, crea una instancia del functor "sobre la marcha", cuando lo necesita, en lugar de crearlo primero, y mantenerlo disponible antes de usarlo para cualquier cosa.
jalf
8
@zadane por supuesto. Solo tienen que tenerlo operator(), porque eso es lo que usa la persona que llama para invocarlo. Qué más tiene el functor de funciones miembro, constructores, operadores y variables miembro depende completamente de usted.
jalf
44
@ rikimaru2013 En el lenguaje de la programación funcional, tienes razón, una función también es un functor, pero en el lenguaje de C ++, el functor es específicamente una clase utilizada como función. Al principio se abusó un poco de la terminología, pero la división es una distinción útil y, por lo tanto, persiste en la actualidad. Si comienza a referirse a las funciones como "functores" en un contexto de C ++, entonces confundirá la conversación.
srm
66
¿Es una clase o una instancia de la clase? En la mayoría de las fuentes, add42se llamaría un functor, no add_x(que es la clase del functor o simplemente la clase del functor). Encuentro esa terminología consistente porque los functores también se denominan objetos de función , no clases de función. ¿Puedes aclarar este punto?
Sergei Tachenov
121

Poca adición. Puede 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 la función boost :: bind and boost :: puedes crear 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 para humanos :)

Evgeny Lazin
fuente
44
¿No debería operator ()ser público en su primer ejemplo, ya que las clases son por defecto privadas?
NathanOliver
44
tal vez en algún momento esta respuesta merezca una actualización, ya que ahora las lambdas son la forma más fácil de obtener un functor de lo que sea
idclev 463035818
102

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
fuente
11
Solo necesito agregar que se pueden usar como un puntero de función.
Martin York
77
@LokiAstari: para aquellos que son nuevos en el concepto, eso podría ser un poco engañoso. Los functores se pueden "usar como", pero no siempre "en lugar de" punteros de función. Por ejemplo, una función que toma un puntero de función no puede tomar un functor en su lugar, incluso si el functor tiene los mismos argumentos y valor de retorno que el puntero de función. Pero, en general, al diseñar, los functores son el camino preferido y teóricamente "más moderno".
MasonWinsauer
¿Por qué regresa el segundo intcuando debería regresar bool? Esto es C ++, no C. Cuando se escribió esta respuesta, ¿ boolno existía?
Fondo La demanda de Mónica
@QPaysTaxes Un error tipográfico, supongo. Probablemente copié y pegué el código del primer ejemplo y olvidé cambiarlo. Ya lo arreglé.
James Curran
1
@Riasat Si Matcher está en una biblioteca, definir Is5 () es bastante simple. Y puede crear Is7 (), Is32 () etc. Además, eso es solo un ejemplo. El functor podría ser mucho más complicado.
James Curran
51

El nombre "functor" se ha usado 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 C ++ de functor. Es mejor usar el objeto de función de nombre lugar de lo que llamamos "functor" en C ++. Así es como otros lenguajes de programación llaman construcciones similares.

Utilizado en lugar de la función simple:

caracteristicas:

  • El objeto de función puede tener estado
  • El objeto de función se ajusta a OOP (se comporta como cualquier otro objeto).

Contras:

  • Aporta más complejidad al programa.

Utilizado en lugar del puntero de 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, genera cierta sobrecarga)

Utilizado en lugar de la función virtual:

caracteristicas:

  • El objeto de función (no virtual) no requiere vtable y despacho de 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)
Doc
fuente
1
¿Puedes explicar estos casos de uso en un ejemplo real? ¿Cómo podemos usar los functores como polimorfismo y puntero de función?
Milad Khajavi
1
¿Qué significa realmente que un functor tiene estado?
erogol
Gracias por señalar que se necesita una clase base para tener algún tipo de polimorfismo. Solo tengo el problema de que tengo que usar un functor en el mismo lugar que un puntero de función simple y la única forma que encontré fue escribir una clase base de functor (ya que no puedo usar cosas de C ++ 11). No estaba seguro si esta sobrecarga tiene sentido hasta que leí tu respuesta.
idclev 463035818
1
@Erogol Un functor es un objeto que soporta la sintaxis foo(arguments). Por lo tanto, puede contener variables; por ejemplo, si tenía una update_password(string)función, es posible que desee realizar un seguimiento de la frecuencia con que sucedió; con un functor, eso puede ser una private long timerepresentación de la marca de tiempo que sucedió por última vez. Con un puntero de función o una función simple, necesitaría usar una variable fuera de su espacio de nombres, que solo está directamente relacionado por la documentación y el uso, en lugar de por definición.l
Fondo de la demanda de Mónica
44
⁺¹ por mencionar que el nombre se ha inventado sin motivo. Acabo de buscar cuál es la relación entre el functor matemático (o funcional si lo desea) y el de C ++.
Hola Angel
41

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

Los functores se usan comúnmente en algoritmos STL. Son útiles porque pueden mantener el estado antes y entre las llamadas a funciones, como un cierre en lenguajes funcionales. Por ejemplo, podría definir un MultiplyByfunctor 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ía 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 puede estar en línea en más casos. Si pasó un puntero de función a transform, a menos que esa llamada se haya insertado y el compilador sepa que siempre le pasa la misma función, no puede alinear la llamada a través del puntero.

Matthew Crumley
fuente
37

Para los novatos como yo entre nosotros: después de un poco de investigación descubrí qué hacía el código publicado por jalf.

Un functor es un objeto de clase o estructura que se puede "llamar" como una función. Esto es posible sobrecargando el () operator. El () operator(no estoy seguro de cómo se llama) puede tomar cualquier número de argumentos. Otros operadores solo toman dos, es decir + operator, solo pueden tomar dos valores (uno en cada lado del operador) y devolver cualquier valor por el que lo haya sobrecargado. Puede ajustar cualquier número de argumentos dentro de un () operatorque es lo que le da su flexibilidad.

Para crear un functor primero debes crear 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 inicializador (que usa un solo operador de dos puntos, algo en lo que también era nuevo) que construye los objetos miembros de la clase con el parámetro declarado previamente para el constructor. Entonces el () operatorestá sobrecargado. Finalmente declaras los objetos privados de la clase o estructura que has creado.

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

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 inexacto o simplemente está mal, ¡no dudes en corregirme!

Johanne Irish
fuente
1
El operador () se llama operador de llamada de función. Supongo que también podrías llamarlo el operador de paréntesis.
Gautam
44
"Este parámetro es en realidad el argumento" parámetroVar "pasado por el constructor que acabamos de escribir" ¿Eh?
Carreras ligeras en órbita el
22

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

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 std::vector<T>ay devuelve std::vector<U>cuando se le da una función Fque toma Tay devuelve a U. No es necesario definir un functor sobre los tipos de contenedor, también se puede definir para cualquier tipo de plantilla, que incluye 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 a 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 functors deben seguir. La primera es la ley de identidad, que establece que si se le asigna al functor una función de identidad, debería ser lo mismo que aplicar la función de identidad al tipo, es decir, fmap(identity, x)debería ser lo 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 se le da al functor 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. Entonces, fmap(std::bind(f, std::bind(g, _1)), x)debería ser lo 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));
Paul Fultz II
fuente
2
Artículo que argumenta que el functor debe usarse correctamente para este significado (ver también en.wikipedia.org/wiki/Functor ), y que usarlo para objetos de función es simplemente descuidado: jackieokay.com/2017/01/26/functors.html It Sin embargo, puede ser demasiado tarde para eso, dado el número de respuestas aquí que solo consideran el significado del objeto de función.
brazalete
2
Esta respuesta debería ser la que tiene> 700 votos a favor. Como alguien que conoce a Haskell mejor que C ++, la lengua de C ++ me desconcertó todo el tiempo.
mschmidt
Teoría de la categoría y C ++? ¿Es la cuenta SO secreta de Bartosz Milewski?
Mateen Ulhaq
1
Puede ser útil resumir las leyes de functor en notación estándar: fmap(id, x) = id(x)y fmap(f ◦ g, x) = fmap(f, fmap(g, x)).
Mateen Ulhaq
@mschmidt mientras functor también significa esto, C ++ sobrecarga el nombre para que signifique lo mismo que "objeto de función"
Caleth
9

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 (por ejemplo, 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.

Este es un desperdicio increíble y una duplicación de código. Normalmente, simplemente pasaría un puntero de función y solo llamaría 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, ¡la función específica requería un perfil de parámetros completamente diferente! A 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 anulado en una clase derivada. Pero, ¿realmente quiero construir toda esta HERENCIA, solo para poder pasar un puntero de función?

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


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

Compañero de viaje
fuente
3
Si su functor llamó diferentes funciones específicas, y estas otras funciones variaron en la cantidad de parámetros que aceptan, ¿significa que su functor aceptó un número variable de argumentos para enviar a estas otras funciones?
johnbakers
44
Puede usted explicar la situación anterior al citar alguna parte del código, soy nuevo en C ++ quiero entender este concepto ..
Sanjeev
3

Excepto por el uso en la devolución de llamada, los functores de C ++ también pueden ayudar a proporcionar un estilo de acceso de agrado de Matlab a una clase de matriz . Hay un ejemplo .

Yantao Xie
fuente
Esto (el ejemplo de la matriz) es un uso simple operator()pero no hace uso de las propiedades del objeto de función.
Renardesque
3

Como se ha repetido, los functores son clases que pueden tratarse como funciones (operador de sobrecarga ()).

Son más útiles para situaciones en las que necesita asociar algunos datos con llamadas repetidas o demoradas a una función.

Por ejemplo, se podría usar una lista vinculada de functores para implementar un sistema básico de rutina síncrona de baja sobrecarga, un despachador de tareas o un análisis de archivos interrumpible. Ejemplos:

/* prints "this is a very simple and poorly used task queue" */
class Functor
{
public:
    std::string output;
    Functor(const std::string& out): output(out){}
    operator()() const
    {
        std::cout << output << " ";
    }
};

int main(int argc, char **argv)
{
    std::list<Functor> taskQueue;
    taskQueue.push_back(Functor("this"));
    taskQueue.push_back(Functor("is a"));
    taskQueue.push_back(Functor("very simple"));
    taskQueue.push_back(Functor("and poorly used"));
    taskQueue.push_back(Functor("task queue"));
    for(std::list<Functor>::iterator it = taskQueue.begin();
        it != taskQueue.end(); ++it)
    {
        *it();
    }
    return 0;
}

/* prints the value stored in "i", then asks you if you want to increment it */
int i;
bool should_increment;
int doSomeWork()
{
    std::cout << "i = " << i << std::endl;
    std::cout << "increment? (enter the number 1 to increment, 0 otherwise" << std::endl;
    std::cin >> should_increment;
    return 2;
}
void doSensitiveWork()
{
     ++i;
     should_increment = false;
}
class BaseCoroutine
{
public:
    BaseCoroutine(int stat): status(stat), waiting(false){}
    void operator()(){ status = perform(); }
    int getStatus() const { return status; }
protected:
    int status;
    bool waiting;
    virtual int perform() = 0;
    bool await_status(BaseCoroutine& other, int stat, int change)
    {
        if(!waiting)
        {
            waiting = true;
        }
        if(other.getStatus() == stat)
        {
            status = change;
            waiting = false;
        }
        return !waiting;
    }
}

class MyCoroutine1: public BaseCoroutine
{
public:
    MyCoroutine1(BaseCoroutine& other): BaseCoroutine(1), partner(other){}
protected:
    BaseCoroutine& partner;
    virtual int perform()
    {
        if(getStatus() == 1)
            return doSomeWork();
        if(getStatus() == 2)
        {
            if(await_status(partner, 1))
                return 1;
            else if(i == 100)
                return 0;
            else
                return 2;
        }
    }
};

class MyCoroutine2: public BaseCoroutine
{
public:
    MyCoroutine2(bool& work_signal): BaseCoroutine(1), ready(work_signal) {}
protected:
    bool& work_signal;
    virtual int perform()
    {
        if(i == 100)
            return 0;
        if(work_signal)
        {
            doSensitiveWork();
            return 2;
        }
        return 1;
    }
};

int main()
{
     std::list<BaseCoroutine* > coroutineList;
     MyCoroutine2 *incrementer = new MyCoroutine2(should_increment);
     MyCoroutine1 *printer = new MyCoroutine1(incrementer);

     while(coroutineList.size())
     {
         for(std::list<BaseCoroutine *>::iterator it = coroutineList.begin();
             it != coroutineList.end(); ++it)
         {
             *it();
             if(*it.getStatus() == 0)
             {
                 coroutineList.erase(it);
             }
         }
     }
     delete printer;
     delete incrementer;
     return 0;
}

Por supuesto, estos ejemplos no son tan útiles en sí mismos. Solo muestran cómo los functors pueden ser útiles, los functors mismos son muy básicos e inflexibles y esto los hace menos útiles que, por ejemplo, lo que proporciona el impulso.

nfries88
fuente
2

Los gtkmm se utilizan para conectar algunos botones GUI a una función o método C ++ real.


Si usa la biblioteca pthread para hacer que su aplicación sea multiproceso, Functors puede ayudarlo.
Para iniciar un hilo, uno de los argumentos del pthread_create(..)es el puntero de 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 que especifique su clase , como class::method. Y otra cosa, la interfaz de su método solo puede ser:

void* method(void* something)

Por lo tanto, no puede ejecutar (de una manera simple y obvia) métodos de su clase en un hilo sin hacer algo extra.

Una muy buena manera de lidiar con hilos en C ++, es crear su propia Threadclase. Si quería ejecutar métodos desde la MyClassclase, lo que hice fue transformar esos métodos enFunctor clases derivadas.

Además, la Threadclase tiene este método: static void* startThread(void* arg)
se utilizará un puntero a este método como argumento para llamar pthread_create(..). Y lo que startThread(..)debería recibir en arg es una void*referencia convertida a una instancia en el montón de cualquier Functorclase derivada, que se convertirá en Functor*cuando se ejecute, y luego se llamará run()método.

erandros
fuente
2

Para agregar, he usado objetos de función para ajustar un método heredado existente al patrón de comando; (único lugar donde la belleza del paradigma OO verdadero OCP sentí); También se agrega aquí el patrón del 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 al patrón de Comando; para esto, primero, debe escribir un adaptador de función miembro para que pueda llamarse como un objeto de función.

Nota: esto es feo, y es posible que pueda usar los ayudantes de enlace de Boost, etc., pero si no puede o no quiere, esta es una forma.

// 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 la llamada.

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 carpeta. 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 enlace

Alex Punnen
fuente
2

Una gran ventaja de implementar funciones como functores es que pueden mantener y reutilizar el estado entre llamadas. Por ejemplo, muchos algoritmos de programación dinámica, como el algoritmo de Wagner-Fischer para calcular la distancia de Levenshtein entre cadenas, funcionan completando una gran tabla de resultados. Es muy ineficiente asignar esta tabla cada vez que se llama a la función, por lo que implementar la función como functor y hacer de la tabla una variable miembro puede mejorar en gran medida el rendimiento.

A continuación se muestra un ejemplo de implementación del algoritmo Wagner-Fischer como functor. Observe cómo se asigna la tabla en el constructor y luego se reutiliza operator(), con el cambio de tamaño según sea necesario.

#include <string>
#include <vector>
#include <algorithm>

template <typename T>
T min3(const T& a, const T& b, const T& c)
{
   return std::min(std::min(a, b), c);
}

class levenshtein_distance 
{
    mutable std::vector<std::vector<unsigned int> > matrix_;

public:
    explicit levenshtein_distance(size_t initial_size = 8)
        : matrix_(initial_size, std::vector<unsigned int>(initial_size))
    {
    }

    unsigned int operator()(const std::string& s, const std::string& t) const
    {
        const size_t m = s.size();
        const size_t n = t.size();
        // The distance between a string and the empty string is the string's length
        if (m == 0) {
            return n;
        }
        if (n == 0) {
            return m;
        }
        // Size the matrix as necessary
        if (matrix_.size() < m + 1) {
            matrix_.resize(m + 1, matrix_[0]);
        }
        if (matrix_[0].size() < n + 1) {
            for (auto& mat : matrix_) {
                mat.resize(n + 1);
            }
        }
        // The top row and left column are prefixes that can be reached by
        // insertions and deletions alone
        unsigned int i, j;
        for (i = 1;  i <= m; ++i) {
            matrix_[i][0] = i;
        }
        for (j = 1; j <= n; ++j) {
            matrix_[0][j] = j;
        }
        // Fill in the rest of the matrix
        for (j = 1; j <= n; ++j) {
            for (i = 1; i <= m; ++i) {
                unsigned int substitution_cost = s[i - 1] == t[j - 1] ? 0 : 1;
                matrix_[i][j] =
                    min3(matrix_[i - 1][j] + 1,                 // Deletion
                    matrix_[i][j - 1] + 1,                      // Insertion
                    matrix_[i - 1][j - 1] + substitution_cost); // Substitution
            }
        }
        return matrix_[m][n];
    }
};
Martin Broadhurst
fuente
1

Functor también se puede utilizar para simular la definición de una función local dentro de una función. Consulte la pregunta y otra .

Pero un functor local no puede acceder a variables automáticas externas. La función lambda (C ++ 11) es una mejor solución.

Yantao Xie
fuente
-10

He "descubierto" un uso muy interesante de los functores: los uso cuando no tengo un buen nombre para un método, ya que un functor es un método sin nombre ;-)

JChMathae
fuente
¿Por qué describe un functor como un "método sin nombre"?
Anderson Green
55
Una función sin nombre se llama lambda.
Paul Fultz II