sintaxis resueltos programa principiantes para lenguaje funciones ejercicios ejemplos comandos codigos codigo basicos c++ functor function-object function-call-operator

resueltos - programa en c++



¿Qué son los funtores de C++ y sus usos? (15)

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


Al igual que se ha repetido, los funtores 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 retrasadas a una función.

Por ejemplo, una lista enlazada de functores se podría usar para implementar un sistema básico de sincronía de bajo costo, 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 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 el impulso.


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.

Esto es un desperdicio increíble, y la duplicación de código. Normalmente solo pasaría un puntero de función, y llamaría a eso en los 3 puntos. (Entonces el código solo necesita 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! 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 invalidado en una clase derivada. Pero, ¿realmente quiero construir toda esta HERENCIA, solo para poder pasar un puntero a una función?

SOLUCIÓN: Entonces, lo que hice fue crear una clase de envoltura (un "Functor") que puede llamar a cualquiera de las funciones que necesitaba. Lo configuro 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 qué 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, que me permitió reducir la duplicación de código de 20 funciones a 1.


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

Los funcionales son comúnmente utilizados en los 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 functor MultiplyBy 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 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 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 la llamada se haya alineado y el compilador sepa que siempre le pasa la misma función, no puede alinear la llamada a través del puntero.


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 C ++ de functor. Es mejor usar el objeto de función de nombre en 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:

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

Contras:

  • Aporta más complejidad al programa.

Utilizado en lugar de puntero de función:

caracteristicas:

  • 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 se extienda a 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 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 se extienda a alguna clase base, lo que, por lo tanto, genera cierta sobrecarga)

Excepto que se utilizan en la devolución de llamada, los funtores de C ++ también pueden ayudar a proporcionar un estilo de acceso de gusto de Matlab a una clase de matriz . Hay un example


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

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


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


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

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

void* method(void* something)

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

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

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


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 me sentí); También añadiendo 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 adaptarlo 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 puede ser que puedas usar los ayudantes de enlace de Boost, etc., pero si no puedes o no quieres, 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 que la clase anterior ayude a llamar.

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. 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; usa 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 link


Para los novatos como yo entre nosotros: después de una pequeña investigación, descubrí lo que hizo el código publicado por Jalf.

Un functor es un objeto de clase o estructura que puede ser "llamado" como una función. Esto es posible mediante la sobrecarga del () operator . El () operator (no está 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 por el que lo haya sobrecargado. Puede encajar cualquier número 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. A esto le sigue en la misma declaración una lista de inicializadores (que usa un solo operador de dos puntos, algo que también era nuevo) que construye los objetos de miembros de clase con el parámetro previamente declarado al constructor. Entonces el () operator está sobrecargado. Finalmente declara los objetos privados de la clase o estructura que ha 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 incorrecto o simplemente equivocado, ¡siéntase libre de corregirme!


Pequeña adición. Puede usar boost::function , para crear functors 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 puede 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 and boost :: function, puede crear un funtor desde el 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 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 :)


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


Un functor es prácticamente una clase que define al operador (). Eso te permite crear objetos que "parecen" 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 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 personalizables.

Como muestran las últimas líneas, a menudo pasa funtores como argumentos a otras funciones como std :: transform u otros algoritmos de biblioteca estándar. Podría hacer lo mismo con un puntero de función normal, excepto, como dije anteriormente, los funtores pueden ser "personalizados" 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 lo que agregó exactamente 1 a su argumento. El functor es general, y agrega con lo que lo haya inicializado, y también son potencialmente más eficientes. En el ejemplo anterior, el compilador sabe exactamente qué función debe llamar std::transform . Debe llamar a add_x::operator() . Eso significa que puede en línea 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 inmediatamente a qué función apunta, así que, a menos que realice algunas optimizaciones globales bastante complejas, tendría que eliminar la referencia del puntero en el tiempo de ejecución y luego realizar la llamada.


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 map . Por ejemplo, podríamos definir un functor para std::vector como este:

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 asigna una función F que toma una T y devuelve una U Un functor no tiene que ser definido sobre tipos de contenedores, también puede ser definido 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))); }

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 funtores deben 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) debería ser la misma que la 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 asigna una composición de dos funciones, debería ser lo mismo que aplicar el funtor 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));


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 de Wagner-Fischer para calcular la distancia de 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 de la tabla una variable miembro puede mejorar considerablemente el rendimiento.

A continuación se muestra un ejemplo de la implementación del algoritmo de 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]; } };