std :: function y std :: bind: ¿qué son y cuándo deben usarse?

127

Sé qué son los functores y cuándo usarlos con stdalgoritmos, pero no he entendido lo que Stroustrup dice sobre ellos en las Preguntas frecuentes de C ++ 11 .

¿Alguien puede explicar qué std::bindy std::functionson, cuando deberían ser utilizados, y dar algunos ejemplos para los novatos?

Sr. Anubis
fuente

Respuestas:

200

std::bindes para aplicación de función parcial .

Es decir, suponga que tiene un objeto de función fque toma 3 argumentos:

f(a,b,c);

Desea un nuevo objeto de función que solo tome dos argumentos, definidos como:

g(a,b) := f(a, 4, b);

ges una "aplicación parcial" de la función f: el argumento del medio ya se ha especificado y quedan dos por recorrer.

Puedes usar std::bindpara obtener g:

auto g = bind(f, _1, 4, _2);

Esto es más conciso que escribir una clase de functor para hacerlo.

Hay más ejemplos en el artículo al que se vincula. Por lo general, lo usa cuando necesita pasar un functor a algún algoritmo. Tiene una función o functor que casi hace el trabajo que desea, pero es más configurable (es decir, tiene más parámetros) que el algoritmo. Por lo tanto, vincula los argumentos a algunos de los parámetros y deja el resto para que el algoritmo complete:

// raise every value in vec to the power of 7
std::transform(vec.begin(), vec.end(), some_output, std::bind(std::pow, _1, 7));

Aquí, powtoma dos parámetros y puede elevar a cualquier potencia, pero todo lo que nos importa es elevar a la potencia de 7.

Como uso ocasional que no es una aplicación de función parcial, bindtambién puede reordenar los argumentos a una función:

auto memcpy_with_the_parameters_in_the_right_flipping_order = bind(memcpy, _2, _1, _3);

No recomiendo usarlo solo porque no le gusta la API, pero tiene posibles usos prácticos, por ejemplo, porque:

not2(bind(less<T>, _2, _1));

es una función menor o igual (suponiendo un orden total, bla, bla). Este ejemplo normalmente no es necesario ya que ya existe un std::less_equal(usa el <=operador en lugar de hacerlo <, por lo que si no son consistentes, es posible que necesite esto, y que también deba visitar al autor de la clase con un clic de búsqueda). Sin embargo, es el tipo de transformación que surge si está utilizando un estilo funcional de programación.

Steve Jessop
fuente
17
También es útil para devoluciones de llamada a funciones miembro:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
rlduffy
15
Buena explicación de enlace. ¿Pero que pasa std::function?
RedX
10
Tu powejemplo no se compila. Como powes una función sobrecargada, debe especificar manualmente qué sobrecarga. El enlace no puede dejar que sea deducido por la persona que llama del functor resultante. Por ejemplostd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM
2
Muy bien explicado, pero a veces std::bindviene junto con el thisuso como el segundo argumento. ¿Puedes por favor elaborar este caso de uso?
Mendes
2
También por "_1" quieres decir std::placeholders::_1. Me llevó un tiempo descubrir por qué esto no se estaba compilando.
terryg
24

Uno de los principales usos de std :: function y std :: bind es como punteros de función más generelizados. Puede usarlo para implementar el mecanismo de devolución de llamada. Uno de los escenarios populares es que tiene alguna función que llevará mucho tiempo ejecutar, pero no desea esperar a que regrese, entonces puede ejecutar esa función en un hilo separado y darle un puntero de función que lo hará devolución de llamada después de que se complete.

Aquí hay un código de muestra sobre cómo usar esto:

class MyClass {
private:
    //just shorthand to avoid long typing
    typedef std::function<void (float result)> TCallback;

    //this function takes long time
    void longRunningFunction(TCallback callback)
    {
        //do some long running task
        //...
        //callback to return result
        callback(result);
    }

    //this function gets called by longRunningFunction after its done
    void afterCompleteCallback(float result)
    {
        std::cout << result;
    }

public:
    int longRunningFunctionAsync()
    {
        //create callback - this equivalent of safe function pointer
        auto callback = std::bind(&MyClass::afterCompleteCallback, 
            this, std::placeholders::_1);

        //normally you want to start below function on seprate thread, 
        //but for illustration we will just do simple call
        longRunningFunction(callback);
    }
};
Shital Shah
fuente
55
Esta es una respuesta genial. He buscado por todas partes para encontrar esta respuesta. Gracias @ShitalShah
terryg
¿Podría agregar una explicación de por qué el enlace ayuda a hacerlo más seguro?
Steven Lu
Mi mal ... no tenía la intención de decir que es más "más seguro". Punteros a funciones normales también están embargo typesafe std :: función es más genérico para trabajar con lambdas, captura de contexto, métodos, etc. miembro
Shital Shah
bind (& MyClass :: afterCompleteCallback, this, std :: placeholders :: _ 1), 2 argumentos para 1 en definición, nulo afterCompleteCallback (resultado flotante), ¿puede explicar esto?
nonock
1
@nonock Para punteros de función de funciones miembro, necesitamos pasar "este" puntero como primer argumento.
sanoj subran
12

std :: bind fue votado en la biblioteca después de la propuesta para incluir boost bind, principalmente es una especialización de función parcial en la que puede corregir algunos parámetros y cambiar otros sobre la marcha. Ahora esta es la forma de biblioteca de hacer lambdas en C ++. Como respondió Steve Jessop

Ahora que C ++ 11 admite funciones lambda, ya no siento ninguna tentación de usar std :: bind. Prefiero usar curry (especialización parcial) con la función de lenguaje que con la función de biblioteca.

Los objetos std :: function son funciones polimórficas. La idea básica es poder hacer referencia a todos los objetos invocables indistintamente.

Le señalaría estos dos enlaces para más detalles:

Funciones de Lambda en C ++ 11: http://www.nullptr.me/2011/10/12/c11-lambda-having-fun-with-brackets/#.UJmXu8XA9Z8

Entidad invocable en C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8

Sarang
fuente
55
std::bindnunca existió sin lambdas: ambas características se introdujeron en C ++ 11. Tuvimos bind1sty bind2ndque eran versiones demacradas de C ++ 11 bind.
MM
5

Lo usé hace mucho tiempo para crear un grupo de subprocesos de complementos en C ++; Como la función estaba tomando tres parámetros, puedes escribir así

Supongamos que su método tiene la firma:

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

Para crear un objeto de función para vincular los tres parámetros, puede hacer así

// 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
};

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

y aquí nosotros como llamarlo

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

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

Para más detalles sangrientos -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Alex Punnen
fuente