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


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

15 answers

Un funtor es más o menos 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 acerca de los 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 se le da. Pero ese valor 42 no está codificado, se especificó como un argumento constructor cuando creamos nuestra instancia de funtor. Podría crear otra víbora, que añadió 27, simplemente llamando al constructor con un valor diferente. Esto los hace muy personalizables.

Como muestran las últimas líneas, a menudo pasa funtores como argumentos a otras funciones como std::transform u otros algoritmos de bibliotecas estándar. Podría hacer lo mismo con un puntero de función regular, 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 agregara exactamente 1 a su argumento. El funtor es general, y agrega lo que sea que lo inicializaste), y también son potencialmente más eficientes. En el ejemplo anterior, el compilador sabe exactamente a qué función std::transform debe llamar. Debería llamar add_x::operator(). Eso significa que puede insertar esa llamada a la 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 inmediatamente cuál función a la que 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 hacer la llamada.

 881
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-19 18:00:54

Poca adición. Usted puede utilizar boost::function, para crear funtores a partir de funciones y métodos, así:

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 boost:: bind y boost:: function puede crear un funtor 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"

Puede crear una lista o un 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 :)

 113
Author: Lazin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-07 11:33:44

Un Funtor 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 funtor 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)
{ ....}
 76
Author: James Curran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-07 11:34:28

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 en C++. Es mejor usar name function object 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:

Características:

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

Contras:

  • Aporta más complejidad al programa.

Usado en lugar del puntero de función:

Características:

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

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 cierta sobrecarga)

Usado en lugar de virtual función:

Características:

  • El objeto de función (no virtual) no requiere vtable y despacho 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 da cierta sobrecarga)
 38
Author: doc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-13 10:46:57

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

Los funtores se usan comúnmente en algoritmos STL. Son útiles porque pueden mantener el estado antes y entre llamadas a funciones, como un cierre en lenguajes funcionales. Por ejemplo, podría definir un funtor MultiplyBy que multiplique 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 objeto MultiplyBy 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 ser inlineada en más casos. Si ha pasado un puntero de función a transform, a menos que esa llamada se haya inlineado y el compilador sepa que siempre le pasa la misma función, no puede insertar la llamada a través del puntero.

 34
Author: Matthew Crumley,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-08 12:03:00

Para los novatos como yo entre nosotros: después de un poco de investigación me di cuenta de lo que el código jalf publicado hizo.

Un funtor es un objeto de clase o estructura que puede ser "llamado" 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, el + operator solo puede tomar dos valores (uno a cada lado del operador) y devolver cualquier valor para el que lo haya sobrecargado. Puede caber cualquier número de argumentos dentro de un () operator que es lo que le da su flexibilidad.

Para crear un funtor primero debe crear su 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 instrucción por una lista inicializadora (que usa un solo operador de dos puntos, algo en lo que también era nuevo) que construye los objetos miembro de la clase con el parámetro declarado previamente al constructor. Entonces el () operator está sobrecargado. Finalmente declare los objetos privados de la clase o estructura que ha creado.

Mi código (encontré los nombres de variables de jalf 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 inexacto o simplemente incorrecto, ¡siéntase libre de corregirme!

 28
Author: Johanne Irish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-22 21:26:14

Un funtor es una función de orden superior que aplica una función a los tipos parametrizados(es decir, templados). Es una generalización de la función de orden superior map. Por ejemplo, podríamos definir un funtor para std::vector así:

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 un std::vector<T> y devuelve std::vector<U> cuando se le da una función F que toma un T y devuelve un U. Un funtor no tiene que ser definido sobre tipos de contenedores, también se puede definir para cualquier tipo de plantilla, incluyendo 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)));
}

He aquí 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 funtores deben seguir. La primera es la ley de identidad, que establece que si al funtor se le da una función de identidad, debe ser lo mismo que aplicar la función de identidad al tipo, es decir fmap(identity, x) debe 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 al funtor se le da una composición de dos funciones, debe ser lo mismo que aplicar el funtor para la primera función y luego de nuevo para la segunda función. Por lo tanto, fmap(std::bind(f, std::bind(g, _1)), x) debe 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));
 17
Author: Paul Fultz II,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-12 17:12:35

Esta es una situación real en la que me vi obligado a usar un Funtor 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 un desperdicio increíble, y duplicación de código. Normalmente me acaba de pasar en un puntero de función, y sólo llamar a que en los 3 puntos. (Así que el código solo tiene que aparecer una vez, en lugar de veinte veces.)

Pero entonces me di cuenta, en cada caso, la función específica requiere 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 sobreescrito en una clase derivada. Pero ¿realmente quiero construir toda esta HERENCIA, solo para que pueda pasar un puntero de función????

SOLUCIÓN: Así que lo que hice fue, hice una clase wrapper (un "Functor") que es capaz de llamar a cualquiera de las funciones que necesitaba llamar. He configurado previamente (con sus parámetros, etc) y luego se lo paso en lugar de un puntero a función. Ahora el código llamado puede activar el Funtor, sin saber lo que está sucediendo en el interior. Incluso puede llamarlo varias veces (lo necesitaba para llamar 3 veces.)


Eso es todo a un ejemplo práctico donde un Funtor 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.

 8
Author: Fellow Traveler,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-26 06:54:56

A excepción de los utilizados en la devolución de llamada, los funtores de C++ también pueden ayudar a proporcionar un Matlab que le guste el estilo de acceso a una clase matrix. Hay un ejemplo.

 2
Author: Shawn Xie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-07 07:03:17

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


Si usa la biblioteca pthread para hacer que su aplicación tenga varios hilos, los Functors pueden ayudarlo.
Para iniciar un subproceso, uno de los argumentos del pthread_create(..) es el puntero de la función que se ejecutará en su propio subproceso.
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 especifique su clase , como class::method. Y otra cosa, la interfaz de su método solo puede ser:

void* method(void* something)

Así que no puedes ejecutar (de una manera simple y obvia), métodos de tu clase en un hilo sin hacer algo extra.

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

También, la clase Thread tiene este método: static void* startThread(void* arg)
Un puntero a esto el método se utilizará como argumento para llamar a pthread_create(..). Y lo que startThread(..) debería recibir en arg es una referencia fundida void* a una instancia en heap de cualquier clase derivada Functor, que se fundirá de nuevo a Functor* cuando se ejecute, y luego se llamará su método run().

 1
Author: erandros,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-24 18:57:24

Para agregar, he utilizado objetos de función para ajustar un método heredado existente al patrón de comandos; (único lugar donde la belleza del paradigma OO verdadero OCP me sentí ); 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 comandos - para esto, primero, debe escribir un adaptador de función miembro para que pueda ser llamado como un objeto de función.

Nota - esto es feo, y puede ser puede utilizar los ayudantes Boost bind, etc., pero si no puedes o no quieres, 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
};

También, necesitamos un método auxiliar 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 enlazar los parámetros, tenemos que escribir una función binder. Así que, 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 lo 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

 1
Author: Alex Punnen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-28 06:50:37

Como se ha repetido, los funtores son clases que pueden ser tratadas como funciones (overload operator ()).

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

Por ejemplo, una lista enlazada de funtores podría usarse para implementar un sistema básico de coroutina 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 ellos mismos. Solo muestran cómo los funtores pueden ser útiles, los funtores en sí mismos son muy básicos e inflexibles y esto los hace menos útiles que, por ejemplo, lo que proporciona boost.

 1
Author: nfries88,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-08 05:34:56

Una gran ventaja de implementar funciones como funtores es que pueden mantener y reutilizar el estado entre llamadas. Por ejemplo, muchos algoritmos de programación dinámica, como el algoritmo Wagner-Fischer para calcular la distancia Levenshtein entre cadenas, funcionan rellenando 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 un funtor y hacer que la tabla sea una variable miembro puede mejorar el rendimiento.

A continuación se muestra un ejemplo de implementación del algoritmo Wagner-Fischer como un funtor. Observe cómo se asigna la tabla en el constructor, y luego se reutiliza en 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];
    }
};
 1
Author: Martin Broadhurst,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-01-16 09:56:58

Functor también se puede usar para simular la definición de una función local dentro de una función. Refiérase a la pregunta y otra .

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

 0
Author: Shawn Xie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:53

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

 -9
Author: JChMathae,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-16 13:29:25