webentwicklung-frage-antwort-db.com.de

Warum funktioniert std :: bind in diesem Beispiel nicht ohne Platzhalter (Member-Funktion)?

Dies ist zum Beispiel meine Mitgliedsfunktion (do_it):

class oops
{
public:
    void do_it(GtkWidget *widget, GdkEvent *event, gpointer data)
    {
        g_print ("Hi there :)\n");
    }
};

... und ich benutze std::bind, um es wie eine Nichtmitgliedsfunktion aussehen zu lassen:

oops o;
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o);

aber es funktioniert nicht, das Folgende ist die Compiler-Fehlermeldung:

program.cc: In function ‘int main(int, char**)’:
program.cc:69:85: error: conversion from ‘std::_Bind_helper<false, void (oops::*)(_GtkWidget*, _GdkEvent*, void*), oops&>::type {aka std::_Bind<std::_Mem_fn<void (oops::*)(_GtkWidget*, _GdkEvent*, void*)>(oops)>}’ to non-scalar type ‘std::function<void(_GtkWidget*, _GdkEvent*, void*)>’ requested
   std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o);
                                                                                     ^

Ich muss es mit std::placeholders beheben:

oops o;
std::function<void(GtkWidget*, GdkEvent*, gpointer)> f = std::bind(&oops::do_it, o, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);

Warum funktioniert es nicht ohne Angabe von std::placeholders?

16
M. Sadeq H. E.

std::bind() soll eine aufrufbare Entität ausführen, die einen (Teil-) Aufruf einer Funktion darstellt. Es funktioniert so, dass einige Parameter des Aufrufs an das erzeugte Aufrufobjekt gebunden werden und der Rest der Parameter an der Stelle des Aufrufs festgelegt wird:

void f(int,int,int);

int main()
{
    std::function<void()> f_call = std::bind( f , 1 , 2 , 3);

    f_call(); //Equivalent to f(1,2,3)
}

Der erste Parameter von std::bind() ist die aufzurufende Funktion und der Rest sind die Argumente des Aufrufs.

In diesem Beispiel wird das Aufrufobjekt mit allen drei angegebenen Parametern generiert, sodass der Aufrufpunkt keine Parameter enthält. Betrachten Sie nun einen teilweise definierten Anruf:

std::function<void(int,int,int)> f_call = std::bind( f );

Dies wird nicht kompiliert, da die Funktion drei Parameter hat und Sie keine angegeben haben! Das macht doch keinen Sinn, oder? Wenn Sie eine Funktion mit drei Parametern haben, sollten Sie dem Aufrufobjekt drei Parameter übergeben.

Wenn Sie angeben müssen, dass einige Parameter am Aufrufpunkt angegeben werden müssen, müssen Sie für die Darstellung dieser Parameter Platzhalter verwenden. Zum Beispiel:

using namespace std::placeholders;

std::function<void(int,int,int)> f_call = std::bind( f , _1 , _2 , _3 );

f_call( 1 , 2 , 3 ); //Same as f(1,2,3)

Wie Sie sehen, haben wir mit Platzhaltern drei "Leerzeichen" für den Funktionsaufruf angegeben, d. H. Drei Parameter, die am Aufrufpunkt angegeben würden.
Beachten Sie, dass die Nummern der Platzhalter die Nummer des Parameters am Aufrufpunkt angeben. Der erste Parameter des Aufrufpunkts wird durch _1, der zweite durch _2 usw. identifiziert. Dies kann verwendet werden, um Parameter auf unterschiedliche Weise anzugeben, die Parameter eines Funktionsaufrufs neu anzuordnen usw. Zum Beispiel:

std::function<void(int,int)> f_call = std::bind( f , _1 , 2 , _2 );

f_call( 1 , 3 ); //Equivalent to f( 1 , 2 , 3 );

std::function<void(int,int,int)> reordered_call = std::bind( f , _3 , _2 , _1 );

reordered_call( 3 , 2 , 1 ); //Same as f( 1 , 2 , 3 );

Schließlich könnte std::bind() verwendet werden, um eine Member-Funktion an das Objekt zu binden, mit dem sie aufgerufen wurde:

struct foo
{
    void f() const;
};

int main()
{
    foo myfoo;
    std::function<void()> f = std::bind( &foo::f , std::cref( myfoo ) );

    f(); //Tah dah!
}

Eine Member-Funktion kann als Funktion mit einem ausgeblendeten Parameter betrachtet werden, bei dem es sich um das Objekt handelt, mit dem der Aufruf erfolgt. Deshalb wird das Objekt als erster Parameter gebunden.

Genau wie in den obigen Beispielen ist wenn Sie nur eine bestimmte Anzahl von Parametern am Bindungspunkt kennen und später am Aufrufpunkt andere angeben müssen, sollten Sie Platzhalter verwenden :

using namespace std::placeholders;

oops o;

std::function<GtkWidget*,GtkEvent*,gpointer> do_it = std::bind( &oops::do_it , std::ref( o ) , _1 , _2 , _3 );

do_it( /* first param */ , /*second param */ , /* third param */ ); //Call

Ein paar Details

Die Signatur des Aufrufobjekts

Beachten Sie, dass wir std::function verwenden, um das Aufrufobjekt zu speichern. Die Signatur dieser Funktion hängt vom Typ des generierten Aufrufobjekts ab, dh hängt von der Art und Weise ab, in der Sie die Parameter am Bindungspunkt angegeben haben .

Ein Aufrufobjekt ist nur eine weitere aufrufbare Entität, die als Aufruf der ursprünglichen Funktion fungiert. Nachfolgend unsere f() Funktionsbeispiele:

std::function<void()> f_call = std:bind( f , 1 , 2 , 3 );

Hier ist die Signatur des Aufrufobjekts void(), da wir an der Bindungsstelle die gesamte Menge der Parameter angegeben haben und am Aufrufpunkt keine Person angegeben werden muss (das Aufrufobjekt hat keine Parameter).

Im Falle eines Teilgesprächs:

std::function<void(int,int,int)> f_call = std::bind( f, _1 , _2 , _3 );

f_call( 1 , 2 , 3 );

die Signatur des Aufrufobjekts ist void(int,int,int), da an der Aufrufstelle drei Parameter angegeben werden müssen (Platzhalter beachten). Im Allgemeinen hat das Aufrufobjekt dieselbe Anzahl von Parametern wie die Platzhalter, die Sie am Bindungspunkt angegeben haben.

44
Manu343726

Beachten Sie, dass die Nummern der Platzhalter die Nummer des Parameters am Aufrufpunkt angeben. Der erste Parameter des Aufrufpunkts ist mit _1, der zweite mit _2 usw. gekennzeichnet.

Ich möchte einige Beispiele für @ Manu343726. Hinzufügen, wenn Sie versuchen, eine Funktion mit 3 Parametern in eine Funktion mit 2 Parametern zu transformieren. Sie können 1 Parameter zum Bindungszeitpunkt angeben und den Rest 2 zum Aufrufzeitpunkt angeben.

typedef std::function<void(int, int)> add2;
int add3(int x1, int x2, int x3) { return x1 + x2 + x3; }
auto fn = std::bind(add3, 11, std::placeholders::_1, std::placeholders::_2);
fn(22, 33)

jetzt ist 11 x1 von add3, _1 ist x2 von add3, _2 ist x3 von add3. _ 2 bedeutet, dass fnzum Aufrufzeitpunkt 2 Parameter hat. Und dieses Format ist falsch:

auto fn = std::bind(add3, 11, std::placeholders::_2, std::placeholders::_3);

0
robert