std :: function e std :: bind: cosa sono e quando dovrebbero essere usati?


129

So cosa sono i funtori e quando usarli con gli stdalgoritmi, ma non ho capito cosa dice Stroustrup su di loro nelle FAQ di C ++ 11 .

Qualcuno può spiegare cosa std::binde std::functionsono, quando dovrebbero essere usati e fornire alcuni esempi per i neofiti?

Risposte:


201

std::bindè per l' applicazione di funzioni parziali .

Cioè, supponi di avere un oggetto funzione fche accetta 3 argomenti:

f(a,b,c);

Vuoi un nuovo oggetto funzione che accetta solo due argomenti, definiti come:

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

gè una "applicazione parziale" della funzione f: l'argomento centrale è già stato specificato e ne rimangono due ancora da completare.

Puoi usare std::bindper ottenere g:

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

Questo è più conciso che scrivere effettivamente una classe funtore per farlo.

Ci sono ulteriori esempi nell'articolo a cui ti colleghi. In genere lo usi quando devi passare un funtore a un algoritmo. Hai una funzione o un funtore che fa quasi il lavoro che desideri, ma è più configurabile (cioè ha più parametri) rispetto a quelli utilizzati dall'algoritmo. Quindi leghi gli argomenti ad alcuni dei parametri e lasci il resto all'algoritmo da compilare:

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

Qui, powprende due parametri e può elevarsi a qualsiasi potenza, ma tutto ciò che ci interessa è elevare alla potenza di 7.

Come uso occasionale che non è un'applicazione di funzioni parziali, bindpuò anche riordinare gli argomenti di una funzione:

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

Non consiglio di usarlo solo perché non ti piace l'API, ma ha potenziali usi pratici, ad esempio perché:

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

è una funzione minore o uguale (assumendo un ordine totale, blah blah). Questo esempio normalmente non è necessario poiché esiste già un std::less_equal(utilizza l' <=operatore anziché <, quindi se non sono coerenti, potresti aver bisogno di questo, e potresti anche dover visitare l'autore della classe con un indizio). Tuttavia, è il tipo di trasformazione che si verifica se si utilizza uno stile di programmazione funzionale.


18
myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
Utile

15
Bella spiegazione del legame. Ma di cosa std::function?
RedX

10
Il tuo powesempio non viene compilato. Poiché powè una funzione sovraccarica, è necessario specificare manualmente quale sovraccarico. Il legame non può lasciarlo dedurre dal chiamante del funtore risultante. Ad esempiostd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM

2
Molto ben spiegato, ma a volte std::bindviene fornito insieme thisall'utilizzo come secondo argomento. Puoi elaborare questo caso d'uso?
Mendes

2
Anche con "_1" intendi std::placeholders::_1. Mi ci è voluto un po 'per scoprire perché non si stava compilando.
terryg

26

Uno degli usi principali di std :: function e std :: bind è come puntatori a funzioni più generici. Puoi usarlo per implementare il meccanismo di callback. Uno degli scenari più diffusi è che hai qualche funzione che richiederà molto tempo per essere eseguita ma non vuoi aspettare che ritorni, quindi puoi eseguire quella funzione su thread separati e dargli un puntatore a funzione che lo farà richiamata al termine.

Ecco un codice di esempio su come utilizzarlo:

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

5
Questa è un'ottima risposta. Ho guardato dappertutto per trovare questa risposta. Grazie @ShitalShah
terryg

Potresti aggiungere una spiegazione del motivo per cui la rilegatura contribuisce a renderlo più sicuro?
Steven Lu

Colpa mia ... non intendevo dire che è più "più sicuro". Anche i normali puntatori a funzione sono tipizzati, tuttavia std :: function è più generico per funzionare con lambda, acquisizione di contesto, metodi membri ecc.
Shital Shah,

bind (& MyClass :: afterCompleteCallback, this, std :: placeholders :: _ 1), 2 args for 1 in definition, void afterCompleteCallback (float result), can tell this?
nonock il

1
@nonock Per i puntatori a funzione delle funzioni membro, è necessario passare il puntatore "this" come primo argomento.
sanoj subran il

12

std :: bind è stato votato come libreria dopo la proposta di includere boost bind, principalmente è una specializzazione parziale della funzione in cui è possibile correggere pochi parametri e modificarne altri al volo. Ora questo è il modo in cui la libreria esegue lambda in C ++. Come ha risposto Steve Jessop

Ora che C ++ 11 supporta le funzioni lambda, non sento più la tentazione di usare std :: bind. Preferirei usare il curry (specializzazione parziale) con la funzione della lingua piuttosto che la funzione della libreria.

Gli oggetti std :: function sono funzioni polimorfiche. L'idea di base è quella di poter fare riferimento a tutti gli oggetti richiamabili in modo intercambiabile.

Ti rimando a questi due link per ulteriori dettagli:

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

Entità richiamabile in C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8


5
std::bindnon è mai esistito senza lambda: entrambe queste funzionalità sono state introdotte in C ++ 11. Avevamo bind1ste bind2ndquali erano versioni emaciate di C ++ 11 bind.
MM

5

L'ho usato molto tempo fa per creare un pool di thread di plugin in C ++; Dato che la funzione prendeva tre parametri, puoi scrivere in questo modo

Supponi che il tuo metodo abbia la firma:

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

Per creare un oggetto funzione per associare i tre parametri puoi fare in questo modo

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

Ora, per legare i parametri, dobbiamo scrivere una funzione binder. Quindi, eccolo:

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

E una funzione di supporto per usare la classe 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);
}

e qui noi come chiamarlo

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

Nota: f3 (); chiamerà il metodo task1-> ThreeParameterTask (21,22,23);

Per ulteriori dettagli cruenti -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.