std :: function und std :: bind: was sind sie und wann sollten sie verwendet werden?


127

Ich weiß, was Funktoren sind und wann stdich sie mit Algorithmen verwenden soll, aber ich habe nicht verstanden, was Stroustrup in den C ++ 11-FAQ über sie sagt .

Kann jemand erklären, was std::bindund std::functionwann sie verwendet werden sollten, und einige Beispiele für Neulinge geben?

Antworten:


200

std::bindist für Teilfunktionsanwendungen .

Angenommen, Sie haben ein Funktionsobjekt, fdas drei Argumente akzeptiert:

f(a,b,c);

Sie möchten ein neues Funktionsobjekt, das nur zwei Argumente akzeptiert, definiert als:

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

gist eine "Teilanwendung" der Funktion f: Das mittlere Argument wurde bereits angegeben, und es sind noch zwei übrig.

Sie können verwenden std::bind, um zu erhalten g:

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

Dies ist prägnanter als das Schreiben einer Funktorklasse, um dies zu tun.

Der Artikel, auf den Sie verlinken, enthält weitere Beispiele. Sie verwenden es im Allgemeinen, wenn Sie einen Funktor an einen Algorithmus übergeben müssen. Sie haben eine Funktion oder einen Funktor, der / die fast die gewünschte Arbeit erledigt, aber konfigurierbarer ist (dh mehr Parameter hat) als der Algorithmus verwendet. Sie binden also Argumente an einige der Parameter und überlassen den Rest dem Algorithmus zum Ausfüllen:

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

Hier werden powzwei Parameter verwendet und können auf jede Potenz erhöht werden , aber alles, was uns wichtig ist, ist die Erhöhung auf die Potenz von 7.

Als gelegentliche Verwendung, die keine Teilfunktionsanwendung ist, bindkönnen die Argumente auch in eine Funktion umgeordnet werden:

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

Ich empfehle nicht, es zu verwenden, nur weil Ihnen die API nicht gefällt, aber es hat potenzielle praktische Verwendungsmöglichkeiten, zum Beispiel, weil:

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

ist eine Funktion, die kleiner oder gleich ist (unter der Annahme einer Gesamtordnung, bla bla). Dieses Beispiel ist normalerweise nicht erforderlich, da es bereits einen gibt std::less_equal(es verwendet <=eher den Operator als <. Wenn diese also nicht konsistent sind, benötigen Sie dies möglicherweise und müssen den Autor der Klasse möglicherweise auch mit einem Cluestick besuchen). Es ist jedoch die Art von Transformation, die sich ergibt, wenn Sie einen funktionalen Programmierstil verwenden.


17
Auch praktisch für Rückrufe auf Mitgliedsfunktionen:myThread=boost::thread(boost::bind(&MyClass::threadMain, this))
Rlduffy

15
Schöne Erklärung der Bindung. Aber was ist mit std::function?
RedX

10
Ihr powBeispiel wird nicht kompiliert. Da powes sich um eine überladene Funktion handelt, müssen Sie manuell angeben, welche Überlastung vorliegt. Die Bindung kann nicht vom Anrufer des resultierenden Funktors abgeleitet werden. ZBstd::transform(vec.begin(), vec.end(), out.begin(), std::bind((double (*)(double, int))std::pow, _1, 7));
MM

2
Sehr gut erklärt, kommt aber manchmal std::bindmit der thisVerwendung als 2. Argument zusammen. Können Sie diesen Anwendungsfall bitte näher erläutern?
Mendes

2
Auch mit "_1" meinen Sie std::placeholders::_1. Ich habe eine Weile gebraucht, um herauszufinden, warum dies nicht kompiliert wurde.
Terryg

24

Eine der Hauptanwendungen von std :: function und std :: bind sind generischere Funktionszeiger. Sie können es verwenden, um einen Rückrufmechanismus zu implementieren. Eines der beliebtesten Szenarien ist, dass Sie eine Funktion haben, deren Ausführung lange dauern wird, aber nicht auf die Rückkehr warten möchten. Dann können Sie diese Funktion in einem separaten Thread ausführen und ihr einen Funktionszeiger geben, den sie ausführen wird Rückruf nach Abschluss.

Hier ist ein Beispielcode für die Verwendung:

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
Dies ist eine großartige Antwort. Ich habe überall nach dieser Antwort gesucht. Vielen Dank @ShitalShah
Terryg

Können Sie eine Erklärung hinzufügen, warum die Bindung die Sicherheit erhöht?
Steven Lu

Mein schlechtes ... Ich wollte nicht sagen, dass es "sicherer" ist. Normale Funktionszeiger sind ebenfalls typsicher, jedoch ist std :: function allgemeiner für die Arbeit mit Lambdas, Kontexterfassung, Mitgliedsmethoden usw.
Shital Shah

bind (& MyClass :: afterCompleteCallback, this, std :: placeholders :: _ 1), 2 Argumente für 1 in Definition, void afterCompleteCallback (float result), kann dies erklären?
Nonock

@nonock Für Funktionszeiger von Elementfunktionen müssen wir "diesen" Zeiger als erstes Argument übergeben.
Sanoj Subran

12

std :: bind wurde nach dem Vorschlag, Boost Bind aufzunehmen, in die Bibliothek gewählt. In erster Linie handelt es sich um eine Teilfunktionsspezialisierung, bei der Sie einige Parameter korrigieren und andere im laufenden Betrieb ändern können. Dies ist eine Bibliotheksmethode für Lambdas in C ++. Wie von Steve Jessop beantwortet

Jetzt, da C ++ 11 Lambda-Funktionen unterstützt, habe ich keine Versuchung mehr, std :: bind zu verwenden. Ich würde lieber Curry (teilweise Spezialisierung) mit Sprachfunktion als Bibliotheksfunktion verwenden.

std :: function-Objekte sind polymorphe Funktionen. Die Grundidee besteht darin, alle aufrufbaren Objekte austauschbar referenzieren zu können.

Ich möchte Sie für weitere Details auf diese beiden Links verweisen:

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

Aufrufbare Entität in C ++: http://www.nullptr.me/2011/05/31/callable-entity/#.UJmXuMXA9Z8


5
std::bindgab es nie ohne Lambdas - beide Funktionen wurden in C ++ 11 eingeführt. Wir hatten bind1stund bind2nddie abgemagerte Versionen von C ++ 11 binden.
MM

5

Ich habe es vor langer Zeit verwendet, um einen Plugin-Thread-Pool in C ++ zu erstellen. Da die Funktion drei Parameter verwendet hat, können Sie so schreiben

Angenommen, Ihre Methode hat die Signatur:

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

So erstellen Sie ein Funktionsobjekt zum Binden der drei Parameter

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

Um die Parameter zu binden, müssen wir nun eine Binder-Funktion schreiben. Also, hier geht es:

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

Und eine Hilfsfunktion zur Verwendung der Klasse 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);
}

und hier uns wie man es nennt

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

Anmerkung: f3 (); ruft die Methode task1-> ThreeParameterTask (21,22,23) auf;

Weitere wichtige Details -> http://www.codeproject.com/Articles/26078/AC-Plug-in-ThreadPool-Design

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.